mondrian-olap 0.4.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. data/.rspec +2 -0
  2. data/Changelog.md +60 -0
  3. data/Gemfile +21 -0
  4. data/LICENSE-Mondrian.html +259 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +302 -0
  7. data/RUNNING_TESTS.rdoc +66 -0
  8. data/Rakefile +48 -0
  9. data/VERSION +1 -0
  10. data/lib/mondrian-olap.rb +1 -0
  11. data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
  12. data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
  13. data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
  14. data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
  15. data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
  16. data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
  17. data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
  18. data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
  19. data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
  20. data/lib/mondrian/jars/javacup.jar +0 -0
  21. data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
  22. data/lib/mondrian/jars/log4j.properties +5 -0
  23. data/lib/mondrian/jars/mondrian.jar +0 -0
  24. data/lib/mondrian/jars/olap4j.jar +0 -0
  25. data/lib/mondrian/olap.rb +17 -0
  26. data/lib/mondrian/olap/connection.rb +201 -0
  27. data/lib/mondrian/olap/cube.rb +297 -0
  28. data/lib/mondrian/olap/error.rb +57 -0
  29. data/lib/mondrian/olap/query.rb +342 -0
  30. data/lib/mondrian/olap/result.rb +264 -0
  31. data/lib/mondrian/olap/schema.rb +378 -0
  32. data/lib/mondrian/olap/schema_element.rb +153 -0
  33. data/lib/mondrian/olap/schema_udf.rb +282 -0
  34. data/mondrian-olap.gemspec +128 -0
  35. data/spec/connection_role_spec.rb +130 -0
  36. data/spec/connection_spec.rb +72 -0
  37. data/spec/cube_spec.rb +318 -0
  38. data/spec/fixtures/MondrianTest.xml +134 -0
  39. data/spec/fixtures/MondrianTestOracle.xml +134 -0
  40. data/spec/mondrian_spec.rb +53 -0
  41. data/spec/query_spec.rb +807 -0
  42. data/spec/rake_tasks.rb +260 -0
  43. data/spec/schema_definition_spec.rb +1249 -0
  44. data/spec/spec_helper.rb +134 -0
  45. data/spec/support/matchers/be_like.rb +24 -0
  46. metadata +278 -0
@@ -0,0 +1,72 @@
1
+ require "spec_helper"
2
+
3
+ describe "Connection" do
4
+
5
+ describe "create" do
6
+ before(:each) do
7
+ @olap = Mondrian::OLAP::Connection.new(CONNECTION_PARAMS_WITH_CATALOG)
8
+ end
9
+
10
+ it "should not be connected before connection" do
11
+ @olap.should_not be_connected
12
+ end
13
+
14
+ it "should be successful" do
15
+ @olap.connect.should be_true
16
+ end
17
+
18
+ end
19
+
20
+ describe "create with catalog content" do
21
+ before(:all) do
22
+ @schema_xml = File.read(CATALOG_FILE)
23
+ end
24
+ it "should be successful" do
25
+ @olap = Mondrian::OLAP::Connection.new(CONNECTION_PARAMS.merge(
26
+ :catalog_content => @schema_xml
27
+ ))
28
+ @olap.connect.should be_true
29
+ end
30
+
31
+ end
32
+
33
+ describe "properties" do
34
+ before(:all) do
35
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
36
+ end
37
+
38
+ it "should be connected" do
39
+ @olap.should be_connected
40
+ end
41
+
42
+ # to check that correct database dialect is loaded by ServiceDiscovery detected class loader
43
+ it "should use corresponding Mondrian dialect" do
44
+ # read private "schema" field
45
+ schema_field = @olap.raw_schema.getClass.getDeclaredField("schema")
46
+ schema_field.setAccessible(true)
47
+ private_schema = schema_field.get(@olap.raw_schema)
48
+ private_schema.getDialect.java_class.name.should == case MONDRIAN_DRIVER
49
+ when 'mysql' then 'mondrian.spi.impl.MySqlDialect'
50
+ when 'postgresql' then 'mondrian.spi.impl.PostgreSqlDialect'
51
+ when 'oracle' then 'mondrian.spi.impl.OracleDialect'
52
+ when 'luciddb' then 'mondrian.spi.impl.LucidDbDialect'
53
+ when 'mssql' then 'mondrian.spi.impl.MicrosoftSqlServerDialect'
54
+ when 'sqlserver' then 'mondrian.spi.impl.MicrosoftSqlServerDialect'
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ describe "close" do
61
+ before(:all) do
62
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
63
+ end
64
+
65
+ it "should not be connected after close" do
66
+ @olap.close
67
+ @olap.should_not be_connected
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,318 @@
1
+ require "spec_helper"
2
+
3
+ describe "Cube" do
4
+ before(:all) do
5
+ @schema = Mondrian::OLAP::Schema.define do
6
+ cube 'Sales' do
7
+ description 'Sales description'
8
+ table 'sales'
9
+ dimension 'Gender', :foreign_key => 'customer_id' do
10
+ description 'Gender description'
11
+ hierarchy :has_all => true, :primary_key => 'id' do
12
+ description 'Gender hierarchy description'
13
+ table 'customers'
14
+ level 'Gender', :column => 'gender', :unique_members => true, :description => 'Gender level description'
15
+ end
16
+ end
17
+ dimension 'Customers', :foreign_key => 'customer_id' do
18
+ hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
19
+ table 'customers'
20
+ level 'Country', :column => 'country', :unique_members => true
21
+ level 'State Province', :column => 'state_province', :unique_members => true
22
+ level 'City', :column => 'city', :unique_members => false
23
+ level 'Name', :column => 'fullname', :unique_members => true
24
+ end
25
+ end
26
+ calculated_member 'Non-USA' do
27
+ dimension 'Customers'
28
+ formula '[Customers].[All Customers] - [Customers].[USA]'
29
+ end
30
+ dimension 'Time', :foreign_key => 'time_id', :type => 'TimeDimension' do
31
+ hierarchy :has_all => false, :primary_key => 'id' do
32
+ table 'time'
33
+ level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
34
+ level 'Quarter', :column => 'quarter', :unique_members => false, :level_type => 'TimeQuarters'
35
+ level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeMonths'
36
+ end
37
+ hierarchy 'Weekly', :has_all => false, :primary_key => 'id' do
38
+ table 'time'
39
+ level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
40
+ level 'Week', :column => 'weak_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeWeeks'
41
+ end
42
+ end
43
+ measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum'
44
+ measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
45
+ measure 'Store Cost', :column => 'store_cost', :aggregator => 'sum', :visible => false
46
+ end
47
+ end
48
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
49
+ end
50
+
51
+ it "should get all cube names" do
52
+ @olap.cube_names.should == ['Sales']
53
+ end
54
+
55
+ it "should get cube by name" do
56
+ @olap.cube('Sales').should be_a(Mondrian::OLAP::Cube)
57
+ end
58
+
59
+ it "should return nil when getting cube with invalid name" do
60
+ @olap.cube('invalid').should be_nil
61
+ end
62
+
63
+ it "should get cube name" do
64
+ @olap.cube('Sales').name.should == 'Sales'
65
+ end
66
+
67
+ it "should get cube description" do
68
+ @olap.cube('Sales').description.should == 'Sales description'
69
+ end
70
+
71
+ describe "dimensions" do
72
+ before(:all) do
73
+ @cube = @olap.cube('Sales')
74
+ @dimension_names = ['Measures', 'Gender', 'Customers', 'Time']
75
+ end
76
+
77
+ it "should get dimension names" do
78
+ @cube.dimension_names.should == @dimension_names
79
+ end
80
+
81
+ it "should get dimensions" do
82
+ @cube.dimensions.map{|d| d.name}.should == @dimension_names
83
+ end
84
+
85
+ it "should get dimension by name" do
86
+ @cube.dimension('Gender').name.should == 'Gender'
87
+ end
88
+
89
+ it "should return nil when getting dimension with invalid name" do
90
+ @cube.dimension('invalid').should be_nil
91
+ end
92
+
93
+ it "should get dimension description" do
94
+ @cube.dimension('Gender').description.should == 'Gender description'
95
+ end
96
+
97
+ it "should get dimension full name" do
98
+ @cube.dimension('Gender').full_name.should == '[Gender]'
99
+ end
100
+
101
+ it "should get measures dimension" do
102
+ @cube.dimension('Measures').should be_measures
103
+ end
104
+
105
+ it "should get dimension type" do
106
+ @cube.dimension('Gender').dimension_type.should == :standard
107
+ @cube.dimension('Time').dimension_type.should == :time
108
+ @cube.dimension('Measures').dimension_type.should == :measures
109
+ end
110
+ end
111
+
112
+ describe "dimension hierarchies" do
113
+ before(:all) do
114
+ @cube = @olap.cube('Sales')
115
+ end
116
+
117
+ it "should get hierarchies" do
118
+ hierarchies = @cube.dimension('Gender').hierarchies
119
+ hierarchies.size.should == 1
120
+ hierarchies[0].name.should == 'Gender'
121
+ end
122
+
123
+ it "should get hierarchy description" do
124
+ hierarchies = @cube.dimension('Gender').hierarchies.first.description.should == 'Gender hierarchy description'
125
+ end
126
+
127
+ it "should get hierarchy names" do
128
+ @cube.dimension('Time').hierarchy_names.should == ['Time', 'Time.Weekly']
129
+ end
130
+
131
+ it "should get hierarchy by name" do
132
+ @cube.dimension('Time').hierarchy('Time.Weekly').name.should == 'Time.Weekly'
133
+ end
134
+
135
+ it "should return nil when getting hierarchy with invalid name" do
136
+ @cube.dimension('Time').hierarchy('invalid').should be_nil
137
+ end
138
+
139
+ it "should get default hierarchy" do
140
+ @cube.dimension('Time').hierarchy.name.should == 'Time'
141
+ end
142
+
143
+ it "should get hierarchy levels" do
144
+ @cube.dimension('Customers').hierarchy.levels.map(&:name).should == ['(All)', 'Country', 'State Province', 'City', 'Name']
145
+ end
146
+
147
+ it "should get hierarchy level names" do
148
+ @cube.dimension('Time').hierarchy.level_names.should == ['Year', 'Quarter', 'Month']
149
+ @cube.dimension('Customers').hierarchy.level_names.should == ['(All)', 'Country', 'State Province', 'City', 'Name']
150
+ end
151
+
152
+ it "should get hierarchy level depths" do
153
+ @cube.dimension('Customers').hierarchy.levels.map(&:depth).should == [0, 1, 2, 3, 4]
154
+ end
155
+
156
+ it "should get hierarchy level members count" do
157
+ @cube.dimension('Gender').hierarchy.levels.map(&:members_count).should == [1, 2]
158
+ end
159
+ end
160
+
161
+ describe "hierarchy values" do
162
+ before(:all) do
163
+ @cube = @olap.cube('Sales')
164
+ end
165
+
166
+ it "should get hierarchy all member" do
167
+ @cube.dimension('Gender').hierarchy.has_all?.should be_true
168
+ @cube.dimension('Gender').hierarchy.all_member_name.should == 'All Genders'
169
+ end
170
+
171
+ it "should not get all member for hierarchy without all member" do
172
+ @cube.dimension('Time').hierarchy.has_all?.should be_false
173
+ @cube.dimension('Time').hierarchy.all_member_name.should be_nil
174
+ end
175
+
176
+ it "should get hierarchy root members" do
177
+ @cube.dimension('Gender').hierarchy.root_members.map(&:name).should == ['All Genders']
178
+ @cube.dimension('Gender').hierarchy.root_member_names.should == ['All Genders']
179
+ @cube.dimension('Time').hierarchy.root_members.map(&:name).should == ['2010', '2011']
180
+ @cube.dimension('Time').hierarchy.root_member_names.should == ['2010', '2011']
181
+ end
182
+
183
+ it "should return child members for specified member" do
184
+ @cube.dimension('Gender').hierarchy.child_names('All Genders').should == ['F', 'M']
185
+ @cube.dimension('Customers').hierarchy.child_names('USA', 'OR').should ==
186
+ ["Albany", "Beaverton", "Corvallis", "Lake Oswego", "Lebanon", "Milwaukie",
187
+ "Oregon City", "Portland", "Salem", "W. Linn", "Woodburn"]
188
+ end
189
+
190
+ it "should return child members for hierarchy" do
191
+ @cube.dimension('Gender').hierarchy.child_names.should == ['F', 'M']
192
+ end
193
+
194
+ it "should not return child members for leaf member" do
195
+ @cube.dimension('Gender').hierarchy.child_names('All Genders', 'F').should == []
196
+ end
197
+
198
+ it "should return nil as child members if parent member not found" do
199
+ @cube.dimension('Gender').hierarchy.child_names('N').should be_nil
200
+ end
201
+
202
+ end
203
+
204
+ describe "level members" do
205
+ before(:all) do
206
+ @cube = @olap.cube('Sales')
207
+ end
208
+
209
+ it "should get level description" do
210
+ @cube.dimension('Gender').hierarchy.level('Gender').description.should == 'Gender level description'
211
+ end
212
+
213
+ it "should return nil when getting level with invalid name" do
214
+ @cube.dimension('Gender').hierarchy.level('invalid').should be_nil
215
+ end
216
+
217
+ it "should get primary hierarchy level members" do
218
+ @cube.dimension('Customers').hierarchy.level('Country').members.
219
+ map(&:name).should == ['Canada', 'Mexico', 'USA']
220
+ end
221
+
222
+ it "should get secondary hierarchy level members" do
223
+ @cube.dimension('Time').hierarchy('Time.Weekly').level('Year').members.
224
+ map(&:name).should == ['2010', '2011']
225
+ end
226
+ end
227
+
228
+ describe "members" do
229
+ before(:all) do
230
+ @cube = @olap.cube('Sales')
231
+ end
232
+
233
+ it "should return member for specified full name" do
234
+ @cube.member('[Gender].[All Genders]').name.should == 'All Genders'
235
+ @cube.member('[Customers].[USA].[OR]').name.should == 'OR'
236
+ end
237
+
238
+ it "should not return member for invalid full name" do
239
+ @cube.member('[Gender].[invalid]').should be_nil
240
+ end
241
+
242
+ it "should return child members for member" do
243
+ @cube.member('[Gender].[All Genders]').children.map(&:name).should == ['F', 'M']
244
+ @cube.member('[Customers].[USA].[OR]').children.map(&:name).should ==
245
+ ["Albany", "Beaverton", "Corvallis", "Lake Oswego", "Lebanon", "Milwaukie",
246
+ "Oregon City", "Portland", "Salem", "W. Linn", "Woodburn"]
247
+ end
248
+
249
+ it "should return empty children array if member does not have children" do
250
+ @cube.member('[Gender].[All Genders].[F]').children.should be_empty
251
+ end
252
+
253
+ it "should return member depth" do
254
+ @cube.member('[Customers].[All Customers]').depth.should == 0
255
+ @cube.member('[Customers].[USA]').depth.should == 1
256
+ @cube.member('[Customers].[USA].[CA]').depth.should == 2
257
+ end
258
+
259
+ it "should return descendants for member at specified level" do
260
+ @cube.member('[Customers].[Mexico]').descendants_at_level('City').map(&:name).should ==
261
+ ["San Andres", "Santa Anita", "Santa Fe", "Tixapan", "Acapulco", "Guadalajara",
262
+ "Mexico City", "Tlaxiaco", "La Cruz", "Orizaba", "Merida", "Camacho", "Hidalgo"]
263
+ end
264
+
265
+ it "should not return descendants for member when upper level specified" do
266
+ @cube.member('[Customers].[Mexico].[DF]').descendants_at_level('Country').should be_nil
267
+ end
268
+
269
+ it "should be drillable when member has descendants" do
270
+ @cube.member('[Customers].[USA]').should be_drillable
271
+ end
272
+
273
+ it "should not be drillable when member has no descendants" do
274
+ @cube.member('[Gender].[F]').should_not be_drillable
275
+ end
276
+
277
+ it "should not be drillable when member is calculated" do
278
+ @cube.member('[Customers].[Non-USA]').should_not be_drillable
279
+ end
280
+
281
+ it "should be calculated when member is calculated" do
282
+ @cube.member('[Customers].[Non-USA]').should be_calculated
283
+ end
284
+
285
+ it "should not be calculated when normal member" do
286
+ @cube.member('[Customers].[USA]').should_not be_calculated
287
+ end
288
+
289
+ it "should be all member when member is all member" do
290
+ @cube.member('[Customers].[All Customers]').should be_all_member
291
+ end
292
+
293
+ it "should not be all member when member is not all member" do
294
+ @cube.member('[Customers].[USA]').should_not be_all_member
295
+ end
296
+
297
+ it "should get dimension type of standard dimension member" do
298
+ @cube.member('[Customers].[USA]').dimension_type.should == :standard
299
+ end
300
+
301
+ it "should get dimension type of measure" do
302
+ @cube.member('[Measures].[Unit Sales]').dimension_type.should == :measures
303
+ end
304
+
305
+ it "should get dimension type of time dimension member" do
306
+ @cube.member('[Time].[2011]').dimension_type.should == :time
307
+ end
308
+
309
+ it "should be visble when member is visible" do
310
+ @cube.member('[Measures].[Store Sales]').should be_visible
311
+ end
312
+
313
+ it "should not be visble when member is not visible" do
314
+ @cube.member('[Measures].[Store Cost]').should_not be_visible
315
+ end
316
+ end
317
+
318
+ end
@@ -0,0 +1,134 @@
1
+ <?xml version="1.0"?>
2
+ <Schema name="MondrianTest">
3
+ <Dimension name="Time" type="TimeDimension">
4
+ <Hierarchy hasAll="false" primaryKey="id">
5
+ <Table name="time"/>
6
+ <Level name="Year" column="the_year" type="Numeric" uniqueMembers="true"
7
+ levelType="TimeYears"/>
8
+ <Level name="Quarter" column="quarter" uniqueMembers="false"
9
+ levelType="TimeQuarters"/>
10
+ <Level name="Month" column="month_of_year" uniqueMembers="false" type="Numeric"
11
+ levelType="TimeMonths"/>
12
+ </Hierarchy>
13
+ <Hierarchy hasAll="true" name="Weekly" primaryKey="id">
14
+ <Table name="time"/>
15
+ <Level name="Year" column="the_year" type="Numeric" uniqueMembers="true"
16
+ levelType="TimeYears"/>
17
+ <Level name="Week" column="week_of_year" type="Numeric" uniqueMembers="false"
18
+ levelType="TimeWeeks"/>
19
+ <Level name="Day" column="day_of_month" uniqueMembers="false" type="Numeric"
20
+ levelType="TimeDays"/>
21
+ </Hierarchy>
22
+ </Dimension>
23
+
24
+ <Dimension name="Product">
25
+ <Hierarchy hasAll="true" primaryKey="id" primaryKeyTable="products">
26
+ <Join leftKey="product_class_id" rightKey="id">
27
+ <Table name="products"/>
28
+ <Table name="product_classes"/>
29
+ </Join>
30
+ <Level name="Product Family" table="product_classes" column="product_family"
31
+ uniqueMembers="true"/>
32
+ <Level name="Product Department" table="product_classes" column="product_department"
33
+ uniqueMembers="false"/>
34
+ <Level name="Product Category" table="product_classes" column="product_category"
35
+ uniqueMembers="false"/>
36
+ <Level name="Product Subcategory" table="product_classes" column="product_subcategory"
37
+ uniqueMembers="false"/>
38
+ <Level name="Brand Name" table="products" column="brand_name" uniqueMembers="false"/>
39
+ <Level name="Product Name" table="products" column="product_name"
40
+ uniqueMembers="true"/>
41
+ </Hierarchy>
42
+ </Dimension>
43
+
44
+ <Cube name="Sales" defaultMeasure="Unit Sales">
45
+ <Table name="sales"/>
46
+ <DimensionUsage name="Time" source="Time" foreignKey="time_id"/>
47
+ <DimensionUsage name="Product" source="Product" foreignKey="product_id"/>
48
+ <Dimension name="Customers" foreignKey="customer_id">
49
+ <Hierarchy hasAll="true" allMemberName="All Customers" primaryKey="id">
50
+ <Table name="customers"/>
51
+ <Level name="Country" column="country" uniqueMembers="true"/>
52
+ <Level name="State Province" column="state_province" uniqueMembers="true"/>
53
+ <Level name="City" column="city" uniqueMembers="false"/>
54
+ <Level name="Name" column="id" type="Numeric" uniqueMembers="true">
55
+ <NameExpression>
56
+ <SQL dialect="oracle">
57
+ fname || ' ' || lname
58
+ </SQL>
59
+ <SQL dialect="postgresql">
60
+ "fname" || ' ' || "lname"
61
+ </SQL>
62
+ <SQL dialect="mysql">
63
+ CONCAT(`customers`.`fname`, ' ', `customers`.`lname`)
64
+ </SQL>
65
+ <SQL dialect="luciddb">
66
+ "fname" || ' ' || "lname"
67
+ </SQL>
68
+ <SQL dialect="generic">
69
+ fullname
70
+ </SQL>
71
+ </NameExpression>
72
+ <OrdinalExpression>
73
+ <SQL dialect="oracle">
74
+ fname || ' ' || lname
75
+ </SQL>
76
+ <SQL dialect="postgresql">
77
+ "fname" || ' ' || "lname"
78
+ </SQL>
79
+ <SQL dialect="mysql">
80
+ CONCAT(`customers`.`fname`, ' ', `customers`.`lname`)
81
+ </SQL>
82
+ <SQL dialect="luciddb">
83
+ "fname" || ' ' || "lname"
84
+ </SQL>
85
+ <SQL dialect="generic">
86
+ fullname
87
+ </SQL>
88
+ </OrdinalExpression>
89
+ <Property name="Gender" column="gender"/>
90
+ </Level>
91
+ </Hierarchy>
92
+ </Dimension>
93
+ <Dimension name="Gender" foreignKey="customer_id">
94
+ <Hierarchy hasAll="true" allMemberName="All Gender" primaryKey="id">
95
+ <Table name="customers"/>
96
+ <Level name="Gender" column="gender" uniqueMembers="true"/>
97
+ </Hierarchy>
98
+ </Dimension>
99
+
100
+ <Measure name="Unit Sales" column="unit_sales" aggregator="sum"
101
+ formatString="Standard"/>
102
+ <Measure name="Store Cost" column="store_cost" aggregator="sum"
103
+ formatString="#,###.00"/>
104
+ <Measure name="Store Sales" column="store_sales" aggregator="sum"
105
+ formatString="#,###.00"/>
106
+ <Measure name="Sales Count" column="product_id" aggregator="count"
107
+ formatString="#,###"/>
108
+ <Measure name="Customer Count" column="customer_id"
109
+ aggregator="distinct-count" formatString="#,###"/>
110
+ <CalculatedMember
111
+ name="Profit"
112
+ dimension="Measures">
113
+ <Formula>[Measures].[Store Sales] - [Measures].[Store Cost]</Formula>
114
+ <CalculatedMemberProperty name="FORMAT_STRING" value="$#,##0.00"/>
115
+ </CalculatedMember>
116
+ <CalculatedMember
117
+ name="Profit last Period"
118
+ dimension="Measures"
119
+ formula="COALESCEEMPTY((Measures.[Profit], [Time].[Time].PREVMEMBER), Measures.[Profit])"
120
+ visible="false">
121
+ <CalculatedMemberProperty name="FORMAT_STRING" value="$#,##0.00"/>
122
+ <CalculatedMemberProperty name="MEMBER_ORDINAL" value="18"/>
123
+ </CalculatedMember>
124
+ <CalculatedMember
125
+ name="Profit Growth"
126
+ dimension="Measures"
127
+ formula="([Measures].[Profit] - [Measures].[Profit last Period]) / [Measures].[Profit last Period]"
128
+ visible="true"
129
+ caption="Gewinn-Wachstum">
130
+ <CalculatedMemberProperty name="FORMAT_STRING" value="0.0%"/>
131
+ </CalculatedMember>
132
+ </Cube>
133
+
134
+ </Schema>