mondrian-olap 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/Changelog.md +38 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +302 -0
  5. data/VERSION +1 -1
  6. data/lib/mondrian/jars/commons-collections-3.2.jar +0 -0
  7. data/lib/mondrian/jars/commons-logging-1.1.1.jar +0 -0
  8. data/lib/mondrian/jars/commons-math-1.1.jar +0 -0
  9. data/lib/mondrian/jars/eigenbase-properties-1.1.2.jar +0 -0
  10. data/lib/mondrian/jars/eigenbase-resgen-1.3.1.jar +0 -0
  11. data/lib/mondrian/jars/eigenbase-xom-1.3.1.jar +0 -0
  12. data/lib/mondrian/jars/{javacup.jar → javacup-10k.jar} +0 -0
  13. data/lib/mondrian/jars/log4j-1.2.14.jar +0 -0
  14. data/lib/mondrian/jars/mondrian.jar +0 -0
  15. data/lib/mondrian/jars/olap4j-1.0.1.539.jar +0 -0
  16. data/lib/mondrian/olap.rb +2 -1
  17. data/lib/mondrian/olap/connection.rb +163 -32
  18. data/lib/mondrian/olap/cube.rb +163 -24
  19. data/lib/mondrian/olap/error.rb +57 -0
  20. data/lib/mondrian/olap/query.rb +52 -17
  21. data/lib/mondrian/olap/result.rb +298 -6
  22. data/lib/mondrian/olap/schema.rb +220 -29
  23. data/lib/mondrian/olap/schema_element.rb +31 -11
  24. data/lib/mondrian/olap/schema_udf.rb +331 -0
  25. data/lib/mondrian/olap/version.rb +5 -0
  26. data/spec/connection_role_spec.rb +130 -0
  27. data/spec/connection_spec.rb +36 -1
  28. data/spec/cube_spec.rb +137 -7
  29. data/spec/fixtures/MondrianTest.xml +4 -4
  30. data/spec/mondrian_spec.rb +53 -0
  31. data/spec/query_spec.rb +294 -11
  32. data/spec/rake_tasks.rb +8 -8
  33. data/spec/schema_definition_spec.rb +845 -26
  34. data/spec/spec_helper.rb +26 -17
  35. data/spec/support/matchers/be_like.rb +2 -2
  36. metadata +296 -237
  37. data/.rspec +0 -2
  38. data/Gemfile +0 -18
  39. data/README.rdoc +0 -221
  40. data/RUNNING_TESTS.rdoc +0 -66
  41. data/Rakefile +0 -46
  42. data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
  43. data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
  44. data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
  45. data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
  46. data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
  47. data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
  48. data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
  49. data/lib/mondrian/jars/olap4j.jar +0 -0
  50. data/mondrian-olap.gemspec +0 -126
@@ -39,6 +39,41 @@ describe "Connection" do
39
39
  @olap.should be_connected
40
40
  end
41
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 "locale" do
61
+ %w(en en_US de de_DE).each do |locale|
62
+ it "should set #{locale} locale from connection parameters" do
63
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG.merge(:locale => locale))
64
+ @olap.locale.should == locale
65
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG.merge(:locale => locale.to_sym))
66
+ @olap.locale.should == locale.to_s
67
+ end
68
+
69
+ it "should set #{locale} locale using setter method" do
70
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
71
+ @olap.locale = locale
72
+ @olap.locale.should == locale
73
+ @olap.locale = locale.to_sym
74
+ @olap.locale.should == locale.to_s
75
+ end
76
+ end
42
77
  end
43
78
 
44
79
  describe "close" do
@@ -53,4 +88,4 @@ describe "Connection" do
53
88
 
54
89
  end
55
90
 
56
- end
91
+ end
@@ -3,24 +3,43 @@ require "spec_helper"
3
3
  describe "Cube" do
4
4
  before(:all) do
5
5
  @schema = Mondrian::OLAP::Schema.define do
6
+ measures_caption 'Measures caption'
7
+
6
8
  cube 'Sales' do
9
+ description 'Sales description'
10
+ caption 'Sales caption'
11
+ annotations :foo => 'bar'
7
12
  table 'sales'
8
13
  dimension 'Gender', :foreign_key => 'customer_id' do
14
+ description 'Gender description'
15
+ caption 'Gender caption'
9
16
  hierarchy :has_all => true, :primary_key => 'id' do
17
+ description 'Gender hierarchy description'
18
+ caption 'Gender hierarchy caption'
19
+ all_member_name 'All Genders'
20
+ all_member_caption 'All Genders caption'
10
21
  table 'customers'
11
- level 'Gender', :column => 'gender', :unique_members => true
22
+ level 'Gender', :column => 'gender', :unique_members => true,
23
+ :description => 'Gender level description', :caption => 'Gender level caption' do
24
+ # Dimension values SQL generated by caption_expression fails on PostgreSQL and MS SQL
25
+ if %w(mysql oracle).include?(MONDRIAN_DRIVER)
26
+ caption_expression do
27
+ sql "'dummy'"
28
+ end
29
+ end
30
+ end
12
31
  end
13
32
  end
14
- dimension 'Customers', :foreign_key => 'customer_id' do
15
- hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
33
+ dimension 'Customers', :foreign_key => 'customer_id', :annotations => {:foo => 'bar'} do
34
+ hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id', :annotations => {:foo => 'bar'} do
16
35
  table 'customers'
17
- level 'Country', :column => 'country', :unique_members => true
36
+ level 'Country', :column => 'country', :unique_members => true, :annotations => {:foo => 'bar'}
18
37
  level 'State Province', :column => 'state_province', :unique_members => true
19
38
  level 'City', :column => 'city', :unique_members => false
20
39
  level 'Name', :column => 'fullname', :unique_members => true
21
40
  end
22
41
  end
23
- calculated_member 'Non-USA' do
42
+ calculated_member 'Non-USA', :annotations => {:foo => 'bar'} do
24
43
  dimension 'Customers'
25
44
  formula '[Customers].[All Customers] - [Customers].[USA]'
26
45
  end
@@ -37,8 +56,9 @@ describe "Cube" do
37
56
  level 'Week', :column => 'weak_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeWeeks'
38
57
  end
39
58
  end
40
- measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum'
59
+ measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum', :annotations => {:foo => 'bar'}
41
60
  measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
61
+ measure 'Store Cost', :column => 'store_cost', :aggregator => 'sum', :visible => false
42
62
  end
43
63
  end
44
64
  @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
@@ -60,6 +80,18 @@ describe "Cube" do
60
80
  @olap.cube('Sales').name.should == 'Sales'
61
81
  end
62
82
 
83
+ it "should get cube description" do
84
+ @olap.cube('Sales').description.should == 'Sales description'
85
+ end
86
+
87
+ it "should get cube caption" do
88
+ @olap.cube('Sales').caption.should == 'Sales caption'
89
+ end
90
+
91
+ it "should get cube annotations" do
92
+ @olap.cube('Sales').annotations.should == {'foo' => 'bar'}
93
+ end
94
+
63
95
  describe "dimensions" do
64
96
  before(:all) do
65
97
  @cube = @olap.cube('Sales')
@@ -78,6 +110,18 @@ describe "Cube" do
78
110
  @cube.dimension('Gender').name.should == 'Gender'
79
111
  end
80
112
 
113
+ it "should return nil when getting dimension with invalid name" do
114
+ @cube.dimension('invalid').should be_nil
115
+ end
116
+
117
+ it "should get dimension description" do
118
+ @cube.dimension('Gender').description.should == 'Gender description'
119
+ end
120
+
121
+ it "should get dimension caption" do
122
+ @cube.dimension('Gender').caption.should == 'Gender caption'
123
+ end
124
+
81
125
  it "should get dimension full name" do
82
126
  @cube.dimension('Gender').full_name.should == '[Gender]'
83
127
  end
@@ -86,11 +130,23 @@ describe "Cube" do
86
130
  @cube.dimension('Measures').should be_measures
87
131
  end
88
132
 
133
+ it "should get measures caption" do
134
+ @cube.dimension('Measures').caption.should == 'Measures caption'
135
+ end
136
+
89
137
  it "should get dimension type" do
90
138
  @cube.dimension('Gender').dimension_type.should == :standard
91
139
  @cube.dimension('Time').dimension_type.should == :time
92
140
  @cube.dimension('Measures').dimension_type.should == :measures
93
141
  end
142
+
143
+ it "should get dimension annotations" do
144
+ @cube.dimension('Customers').annotations.should == {'foo' => 'bar'}
145
+ end
146
+
147
+ it "should get dimension empty annotations" do
148
+ @cube.dimension('Gender').annotations.should == {}
149
+ end
94
150
  end
95
151
 
96
152
  describe "dimension hierarchies" do
@@ -104,6 +160,14 @@ describe "Cube" do
104
160
  hierarchies[0].name.should == 'Gender'
105
161
  end
106
162
 
163
+ it "should get hierarchy description" do
164
+ hierarchies = @cube.dimension('Gender').hierarchies.first.description.should == 'Gender hierarchy description'
165
+ end
166
+
167
+ it "should get hierarchy caption" do
168
+ hierarchies = @cube.dimension('Gender').hierarchies.first.caption.should == 'Gender hierarchy caption'
169
+ end
170
+
107
171
  it "should get hierarchy names" do
108
172
  @cube.dimension('Time').hierarchy_names.should == ['Time', 'Time.Weekly']
109
173
  end
@@ -112,6 +176,10 @@ describe "Cube" do
112
176
  @cube.dimension('Time').hierarchy('Time.Weekly').name.should == 'Time.Weekly'
113
177
  end
114
178
 
179
+ it "should return nil when getting hierarchy with invalid name" do
180
+ @cube.dimension('Time').hierarchy('invalid').should be_nil
181
+ end
182
+
115
183
  it "should get default hierarchy" do
116
184
  @cube.dimension('Time').hierarchy.name.should == 'Time'
117
185
  end
@@ -132,6 +200,14 @@ describe "Cube" do
132
200
  it "should get hierarchy level members count" do
133
201
  @cube.dimension('Gender').hierarchy.levels.map(&:members_count).should == [1, 2]
134
202
  end
203
+
204
+ it "should get hierarchy annotations" do
205
+ @cube.dimension('Customers').hierarchy.annotations.should == {'foo' => 'bar'}
206
+ end
207
+
208
+ it "should get hierarchy empty annotations" do
209
+ @cube.dimension('Gender').hierarchy.annotations.should == {}
210
+ end
135
211
  end
136
212
 
137
213
  describe "hierarchy values" do
@@ -177,11 +253,23 @@ describe "Cube" do
177
253
 
178
254
  end
179
255
 
180
- describe "level members" do
256
+ describe "hierarchy levels" do
181
257
  before(:all) do
182
258
  @cube = @olap.cube('Sales')
183
259
  end
184
260
 
261
+ it "should get level description" do
262
+ @cube.dimension('Gender').hierarchy.level('Gender').description.should == 'Gender level description'
263
+ end
264
+
265
+ it "should get level caption" do
266
+ @cube.dimension('Gender').hierarchy.level('Gender').caption.should == 'Gender level caption'
267
+ end
268
+
269
+ it "should return nil when getting level with invalid name" do
270
+ @cube.dimension('Gender').hierarchy.level('invalid').should be_nil
271
+ end
272
+
185
273
  it "should get primary hierarchy level members" do
186
274
  @cube.dimension('Customers').hierarchy.level('Country').members.
187
275
  map(&:name).should == ['Canada', 'Mexico', 'USA']
@@ -191,6 +279,15 @@ describe "Cube" do
191
279
  @cube.dimension('Time').hierarchy('Time.Weekly').level('Year').members.
192
280
  map(&:name).should == ['2010', '2011']
193
281
  end
282
+
283
+ it "should get level annotations" do
284
+ @cube.dimension('Customers').hierarchy.level('Country').annotations.should == {'foo' => 'bar'}
285
+ end
286
+
287
+ it "should get level empty annotations" do
288
+ @cube.dimension('Gender').hierarchy.level('Gender').annotations.should == {}
289
+ end
290
+
194
291
  end
195
292
 
196
293
  describe "members" do
@@ -203,6 +300,15 @@ describe "Cube" do
203
300
  @cube.member('[Customers].[USA].[OR]').name.should == 'OR'
204
301
  end
205
302
 
303
+ it "should return all member caption" do
304
+ @cube.member('[Gender].[All Genders]').caption.should == 'All Genders caption'
305
+ end
306
+
307
+ it "should return member caption from expression" do
308
+ @cube.member('[Gender].[F]').caption.should ==
309
+ (%w(mysql oracle).include?(MONDRIAN_DRIVER) ? 'dummy' : 'F')
310
+ end
311
+
206
312
  it "should not return member for invalid full name" do
207
313
  @cube.member('[Gender].[invalid]').should be_nil
208
314
  end
@@ -250,6 +356,10 @@ describe "Cube" do
250
356
  @cube.member('[Customers].[Non-USA]').should be_calculated
251
357
  end
252
358
 
359
+ it "should not be calculated in query when calculated member defined in schema" do
360
+ @cube.member('[Customers].[Non-USA]').should_not be_calculated_in_query
361
+ end
362
+
253
363
  it "should not be calculated when normal member" do
254
364
  @cube.member('[Customers].[USA]').should_not be_calculated
255
365
  end
@@ -274,6 +384,26 @@ describe "Cube" do
274
384
  @cube.member('[Time].[2011]').dimension_type.should == :time
275
385
  end
276
386
 
387
+ it "should be visble when member is visible" do
388
+ @cube.member('[Measures].[Store Sales]').should be_visible
389
+ end
390
+
391
+ it "should not be visble when member is not visible" do
392
+ @cube.member('[Measures].[Store Cost]').should_not be_visible
393
+ end
394
+
395
+ it "should get measure annotations" do
396
+ @cube.member('[Measures].[Unit Sales]').annotations.should == {'foo' => 'bar'}
397
+ end
398
+
399
+ it "should get measure empty annotations" do
400
+ @cube.member('[Measures].[Store Sales]').annotations.should == {}
401
+ end
402
+
403
+ it "should get member empty annotations" do
404
+ @cube.member('[Customers].[USA]').annotations.should == {}
405
+ end
406
+
277
407
  end
278
408
 
279
409
  end
@@ -56,11 +56,11 @@
56
56
  <SQL dialect="oracle">
57
57
  fname || ' ' || lname
58
58
  </SQL>
59
- <SQL dialect="postgres">
59
+ <SQL dialect="postgresql">
60
60
  "fname" || ' ' || "lname"
61
61
  </SQL>
62
62
  <SQL dialect="mysql">
63
- CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)
63
+ CONCAT(`customers`.`fname`, ' ', `customers`.`lname`)
64
64
  </SQL>
65
65
  <SQL dialect="luciddb">
66
66
  "fname" || ' ' || "lname"
@@ -73,11 +73,11 @@ fullname
73
73
  <SQL dialect="oracle">
74
74
  fname || ' ' || lname
75
75
  </SQL>
76
- <SQL dialect="postgres">
76
+ <SQL dialect="postgresql">
77
77
  "fname" || ' ' || "lname"
78
78
  </SQL>
79
79
  <SQL dialect="mysql">
80
- CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)
80
+ CONCAT(`customers`.`fname`, ' ', `customers`.`lname`)
81
81
  </SQL>
82
82
  <SQL dialect="luciddb">
83
83
  "fname" || ' ' || "lname"
@@ -0,0 +1,53 @@
1
+ require "spec_helper"
2
+
3
+ describe "Mondrian features" do
4
+ before(:all) do
5
+ @schema = Mondrian::OLAP::Schema.define do
6
+ cube 'Sales' do
7
+ table 'sales'
8
+ dimension 'Gender', :foreign_key => 'customer_id' do
9
+ hierarchy :has_all => true, :primary_key => 'id' do
10
+ table 'customers'
11
+ level 'Gender', :column => 'gender', :unique_members => true
12
+ end
13
+ end
14
+ dimension 'Customers', :foreign_key => 'customer_id' do
15
+ hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
16
+ table 'customers'
17
+ level 'Country', :column => 'country', :unique_members => true
18
+ level 'State Province', :column => 'state_province', :unique_members => true
19
+ level 'City', :column => 'city', :unique_members => false
20
+ level 'Name', :column => 'fullname', :unique_members => true
21
+ end
22
+ end
23
+ dimension 'Time', :foreign_key => 'time_id', :type => 'TimeDimension' do
24
+ hierarchy :has_all => false, :primary_key => 'id' do
25
+ table 'time'
26
+ level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
27
+ level 'Quarter', :column => 'quarter', :unique_members => false, :level_type => 'TimeQuarters'
28
+ level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeMonths'
29
+ end
30
+ hierarchy 'Weekly', :has_all => false, :primary_key => 'id' do
31
+ table 'time'
32
+ level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
33
+ level 'Week', :column => 'weak_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeWeeks'
34
+ end
35
+ end
36
+ measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum'
37
+ measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
38
+ end
39
+ end
40
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
41
+ end
42
+
43
+ # test for http://jira.pentaho.com/browse/MONDRIAN-1050
44
+ it "should order rows by DateTime expression" do
45
+ lambda do
46
+ @olap.from('Sales').
47
+ columns('[Measures].[Unit Sales]').
48
+ rows('[Customers].children').order('Now()', :asc).
49
+ execute
50
+ end.should_not raise_error
51
+ end
52
+
53
+ end
@@ -165,6 +165,13 @@ describe "Query" do
165
165
  end
166
166
  end
167
167
 
168
+ describe "nonempty_crossjoin" do
169
+ it "should do nonempty_crossjoin of several dimensions" do
170
+ @query.rows('[Product].children').nonempty_crossjoin('[Customers].[Canada]', '[Customers].[USA]')
171
+ @query.rows.should == [:nonempty_crossjoin, ['[Product].children'], ['[Customers].[Canada]', '[Customers].[USA]']]
172
+ end
173
+ end
174
+
168
175
  describe "nonempty" do
169
176
  it "should limit to set of members with nonempty values" do
170
177
  @query.rows('[Product].children').nonempty
@@ -225,6 +232,12 @@ describe "Query" do
225
232
  [:hierarchize, ['[Customers].[Country].Members', '[Customers].[City].Members']]]
226
233
  end
227
234
 
235
+ it "should hierarchize last set of nonempty_crossjoin" do
236
+ @query.rows('[Product].children').nonempty_crossjoin('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize
237
+ @query.rows.should == [:nonempty_crossjoin, ['[Product].children'],
238
+ [:hierarchize, ['[Customers].[Country].Members', '[Customers].[City].Members']]]
239
+ end
240
+
228
241
  it "should hierarchize all crossjoin" do
229
242
  @query.rows('[Product].children').crossjoin('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize_all
230
243
  @query.rows.should == [:hierarchize, [:crossjoin, ['[Product].children'],
@@ -249,6 +262,12 @@ describe "Query" do
249
262
  @query.rows.should == [:crossjoin, ['[Product].children'],
250
263
  [:except, ['[Customers].[Country].Members'], ['[Customers].[USA]']]]
251
264
  end
265
+
266
+ it "should except from last set of nonempty_crossjoin" do
267
+ @query.rows('[Product].children').nonempty_crossjoin('[Customers].[Country].Members').except('[Customers].[USA]')
268
+ @query.rows.should == [:nonempty_crossjoin, ['[Product].children'],
269
+ [:except, ['[Customers].[Country].Members'], ['[Customers].[USA]']]]
270
+ end
252
271
  end
253
272
 
254
273
  describe "filter" do
@@ -283,6 +302,11 @@ describe "Query" do
283
302
  @query.where('[Customers].[USA]').crossjoin('[Time].[2011].[Q1]', '[Time].[2011].[Q2]')
284
303
  @query.where.should == [:crossjoin, ['[Customers].[USA]'], ['[Time].[2011].[Q1]', '[Time].[2011].[Q2]']]
285
304
  end
305
+
306
+ it "should do nonempty_crossjoin of where conditions" do
307
+ @query.where('[Customers].[USA]').nonempty_crossjoin('[Time].[2011].[Q1]', '[Time].[2011].[Q2]')
308
+ @query.where.should == [:nonempty_crossjoin, ['[Customers].[USA]'], ['[Time].[2011].[Q1]', '[Time].[2011].[Q2]']]
309
+ end
286
310
  end
287
311
 
288
312
  describe "with member" do
@@ -329,6 +353,15 @@ describe "Query" do
329
353
  ]
330
354
  ]
331
355
  end
356
+
357
+ it "should accept definition with nonempty_crossjoin" do
358
+ @query.with_set('SelectedRows').as('[Product].children').nonempty_crossjoin('[Customers].[Canada]', '[Customers].[USA]')
359
+ @query.with.should == [
360
+ [ :set, 'SelectedRows',
361
+ [:nonempty_crossjoin, ['[Product].children'], ['[Customers].[Canada]', '[Customers].[USA]']]
362
+ ]
363
+ ]
364
+ end
332
365
  end
333
366
 
334
367
  describe "to MDX" do
@@ -380,6 +413,18 @@ describe "Query" do
380
413
  SQL
381
414
  end
382
415
 
416
+ it "should return query with nonempty_crossjoin" do
417
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
418
+ rows('[Product].children').nonempty_crossjoin('[Customers].[Canada]', '[Customers].[USA]').
419
+ where('[Time].[2010].[Q1]').
420
+ to_mdx.should be_like <<-SQL
421
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
422
+ NONEMPTYCROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]}) ON ROWS
423
+ FROM [Sales]
424
+ WHERE ([Time].[2010].[Q1])
425
+ SQL
426
+ end
427
+
383
428
  it "should return query with where with several same dimension members" do
384
429
  @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
385
430
  rows('[Product].children').
@@ -392,6 +437,18 @@ describe "Query" do
392
437
  SQL
393
438
  end
394
439
 
440
+ it "should return query with where with several different dimension members returned by function" do
441
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
442
+ rows('[Product].children').
443
+ where('Head([Customers].Members).Item(0)', 'Head([Gender].Members).Item(0)').
444
+ to_mdx.should be_like <<-SQL
445
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
446
+ [Product].children ON ROWS
447
+ FROM [Sales]
448
+ WHERE (Head([Customers].Members).Item(0), Head([Gender].Members).Item(0))
449
+ SQL
450
+ end
451
+
395
452
  it "should return query with where with crossjoin" do
396
453
  @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
397
454
  rows('[Product].children').
@@ -400,7 +457,19 @@ describe "Query" do
400
457
  SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
401
458
  [Product].children ON ROWS
402
459
  FROM [Sales]
403
- WHERE CROSSJOIN([Customers].[USA], {[Time].[2011].[Q1], [Time].[2011].[Q2]})
460
+ WHERE CROSSJOIN({[Customers].[USA]}, {[Time].[2011].[Q1], [Time].[2011].[Q2]})
461
+ SQL
462
+ end
463
+
464
+ it "should return query with where with nonempty_crossjoin" do
465
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
466
+ rows('[Product].children').
467
+ where('[Customers].[USA]').nonempty_crossjoin('[Time].[2011].[Q1]', '[Time].[2011].[Q2]').
468
+ to_mdx.should be_like <<-SQL
469
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
470
+ [Product].children ON ROWS
471
+ FROM [Sales]
472
+ WHERE NONEMPTYCROSSJOIN({[Customers].[USA]}, {[Time].[2011].[Q1], [Time].[2011].[Q2]})
404
473
  SQL
405
474
  end
406
475
 
@@ -501,7 +570,7 @@ describe "Query" do
501
570
  rows('[Customers].[Country].Members').except('[Customers].[USA]').
502
571
  to_mdx.should be_like <<-SQL
503
572
  SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
504
- EXCEPT([Customers].[Country].Members, [Customers].[USA]) ON ROWS
573
+ EXCEPT([Customers].[Country].Members, {[Customers].[USA]}) ON ROWS
505
574
  FROM [Sales]
506
575
  SQL
507
576
  end
@@ -540,21 +609,21 @@ describe "Query" do
540
609
  @query.
541
610
  with_member('[Measures].[ProfitPct]').
542
611
  as('Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
543
- :solve_order => 1, :format_string => 'Percent').
612
+ :solve_order => 1, :format_string => 'Percent', :caption => 'Profit %').
544
613
  with_member('[Measures].[ProfitValue]').
545
614
  as('[Measures].[Store Sales] * [Measures].[ProfitPct]',
546
- :solve_order => 2, :format_string => 'Currency').
615
+ :solve_order => 2, :cell_formatter => 'CurrencyFormatter').
547
616
  columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
548
617
  rows('[Product].children').
549
618
  where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
550
619
  to_mdx.should be_like <<-SQL
551
620
  WITH
552
- MEMBER [Measures].[ProfitPct] AS
621
+ MEMBER [Measures].[ProfitPct] AS
553
622
  'Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
554
- SOLVE_ORDER = 1, FORMAT_STRING = 'Percent'
555
- MEMBER [Measures].[ProfitValue] AS
623
+ SOLVE_ORDER = 1, FORMAT_STRING = 'Percent', $caption = 'Profit %'
624
+ MEMBER [Measures].[ProfitValue] AS
556
625
  '[Measures].[Store Sales] * [Measures].[ProfitPct]',
557
- SOLVE_ORDER = 2, FORMAT_STRING = 'Currency'
626
+ SOLVE_ORDER = 2, CELL_FORMATTER = 'rubyobj.Mondrian.OLAP.Schema.CellFormatter.CurrencyFormatterUdf'
558
627
  SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
559
628
  [Product].children ON ROWS
560
629
  FROM [Sales]
@@ -571,11 +640,11 @@ describe "Query" do
571
640
  rows('SelectedRows').
572
641
  to_mdx.should be_like <<-SQL
573
642
  WITH
574
- SET SelectedRows AS
643
+ SET SelectedRows AS
575
644
  'CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]})'
576
645
  MEMBER [Measures].[Profit] AS
577
646
  '[Measures].[Store Sales] - [Measures].[Store Cost]'
578
- SELECT [Measures].[Profit] ON COLUMNS,
647
+ SELECT {[Measures].[Profit]} ON COLUMNS,
579
648
  SelectedRows ON ROWS
580
649
  FROM [Sales]
581
650
  SQL
@@ -612,4 +681,218 @@ describe "Query" do
612
681
 
613
682
  end
614
683
 
615
- end
684
+ describe "errors" do
685
+ before(:each) do
686
+ @query = @olap.from('Sales')
687
+ end
688
+
689
+ it "should raise error when invalid MDX statement" do
690
+ expect {
691
+ @olap.execute "SELECT dummy FROM"
692
+ }.to raise_error {|e|
693
+ e.should be_kind_of(Mondrian::OLAP::Error)
694
+ e.message.should == 'org.olap4j.OlapException: mondrian gave exception while parsing query'
695
+ e.root_cause_message.should == "Syntax error at line 1, column 14, token 'FROM'"
696
+ }
697
+ end
698
+
699
+ it "should raise error when invalid MDX object" do
700
+ expect {
701
+ @query.columns('[Measures].[Dummy]').execute
702
+ }.to raise_error {|e|
703
+ e.should be_kind_of(Mondrian::OLAP::Error)
704
+ e.message.should == 'org.olap4j.OlapException: mondrian gave exception while parsing query'
705
+ e.root_cause_message.should == "MDX object '[Measures].[Dummy]' not found in cube 'Sales'"
706
+ }
707
+ end
708
+
709
+ it "should raise error when invalid formula" do
710
+ expect {
711
+ @query.with_member('[Measures].[Dummy]').as('Dummy(123)').
712
+ columns('[Measures].[Dummy]').execute
713
+ }.to raise_error {|e|
714
+ e.should be_kind_of(Mondrian::OLAP::Error)
715
+ e.message.should == 'org.olap4j.OlapException: mondrian gave exception while parsing query'
716
+ e.root_cause_message.should == "No function matches signature 'Dummy(<Numeric Expression>)'"
717
+ }
718
+ end
719
+
720
+ end
721
+
722
+ describe "drill through cell" do
723
+ before(:all) do
724
+ @query = @olap.from('Sales')
725
+ @result = @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
726
+ rows('[Product].children').
727
+ where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
728
+ execute
729
+ @drill_through = @result.drill_through(:row => 0, :column => 0)
730
+ end
731
+
732
+ it "should return column types" do
733
+ @drill_through.column_types.should == [
734
+ :INT, :VARCHAR, :INT, :INT, :INT,
735
+ :VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR,
736
+ :VARCHAR, :VARCHAR, :VARCHAR, :INT,
737
+ :VARCHAR,
738
+ :DECIMAL
739
+ ]
740
+ end if MONDRIAN_DRIVER == 'mysql'
741
+
742
+ it "should return column names" do
743
+ # ignore calculated customer full name column name which is shown differently on each database
744
+ @drill_through.column_names[0..12].should == %w(
745
+ the_year quarter month_of_year week_of_year day_of_month
746
+ product_family product_department product_category product_subcategory brand_name product_name
747
+ state_province city
748
+ )
749
+ @drill_through.column_names[14..16].should == %w(
750
+ id gender unit_sales
751
+ )
752
+ end if %w(mysql postgresql).include? MONDRIAN_DRIVER
753
+
754
+ it "should return table names" do
755
+ @drill_through.table_names.should == [
756
+ "time", "time", "time", "time", "time",
757
+ "product_classes", "product_classes", "product_classes", "product_classes", "products", "products",
758
+ "customers", "customers", "", "customers",
759
+ "customers",
760
+ "sales"
761
+ ]
762
+ end if %w(mysql postgresql).include? MONDRIAN_DRIVER
763
+
764
+ it "should return column labels" do
765
+ @drill_through.column_labels.should == [
766
+ "Year", "Quarter", "Month", "Week", "Day",
767
+ "Product Family", "Product Department", "Product Category", "Product Subcategory", "Brand Name", "Product Name",
768
+ "State Province", "City", "Name", "Name (Key)",
769
+ "Gender",
770
+ "Unit Sales"
771
+ ]
772
+ end
773
+
774
+ it "should return row values" do
775
+ @drill_through.rows.size.should == 15 # number of generated test rows
776
+ end
777
+
778
+ it "should return correct row value types" do
779
+ @drill_through.rows.first.map(&:class).should ==
780
+ case MONDRIAN_DRIVER
781
+ when "oracle"
782
+ [
783
+ BigDecimal, String, BigDecimal, BigDecimal, BigDecimal,
784
+ String, String, String, String, String, String,
785
+ String, String, String, BigDecimal,
786
+ String,
787
+ BigDecimal
788
+ ]
789
+ when 'mssql'
790
+ [
791
+ Fixnum, String, Fixnum, Fixnum, Fixnum,
792
+ String, String, String, String, String, String,
793
+ String, String, String, BigDecimal,
794
+ String,
795
+ BigDecimal
796
+ ]
797
+ else
798
+ [
799
+ Fixnum, String, Fixnum, Fixnum, Fixnum,
800
+ String, String, String, String, String, String,
801
+ String, String, String, Fixnum,
802
+ String,
803
+ BigDecimal
804
+ ]
805
+ end
806
+ end
807
+
808
+ it "should return only specified max rows" do
809
+ drill_through = @result.drill_through(:row => 0, :column => 0, :max_rows => 10)
810
+ drill_through.rows.size.should == 10
811
+ end
812
+ end
813
+
814
+ describe "drill through cell with return" do
815
+ before(:all) do
816
+ @query = @olap.from('Sales')
817
+ @result = @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
818
+ rows('[Product].children').
819
+ # where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
820
+ where('[Time].[2010].[Q1]').
821
+ execute
822
+ end
823
+
824
+ it "should return only specified fields in specified order" do
825
+ @drill_through = @result.drill_through(:row => 0, :column => 0, :return => [
826
+ '[Time].[Month]',
827
+ '[Customers].[City]',
828
+ '[Product].[Product Family]',
829
+ '[Measures].[Unit Sales]', '[Measures].[Store Sales]'
830
+ ])
831
+ @drill_through.column_labels.should == [
832
+ "Month",
833
+ "City",
834
+ "Product Family",
835
+ "Unit Sales", "Store Sales"
836
+ ]
837
+ end
838
+
839
+ it "should return only nonempty measures" do
840
+ @drill_through = @result.drill_through(:row => 0, :column => 0,
841
+ :return => "[Measures].[Unit Sales], [Measures].[Store Sales]",
842
+ :nonempty => "[Measures].[Unit Sales]"
843
+ )
844
+ @drill_through.column_labels.should == [
845
+ "Unit Sales", "Store Sales"
846
+ ]
847
+ @drill_through.rows.all?{|r| r.any?{|c| c}}.should be_true
848
+ end
849
+
850
+ end
851
+
852
+ describe "drill through statement" do
853
+ before(:all) do
854
+ @query = @olap.from('Sales').
855
+ columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
856
+ rows('[Product].children').
857
+ where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]')
858
+ end
859
+
860
+ it "should return column labels" do
861
+ @drill_through = @query.execute_drill_through
862
+ @drill_through.column_labels.should == [
863
+ "Year", "Quarter", "Month", "Week", "Day",
864
+ "Product Family", "Product Department", "Product Category", "Product Subcategory", "Brand Name", "Product Name",
865
+ "State Province", "City", "Name", "Name (Key)",
866
+ "Gender",
867
+ "Unit Sales"
868
+ ]
869
+ end
870
+
871
+ it "should return row values" do
872
+ @drill_through = @query.execute_drill_through
873
+ @drill_through.rows.size.should == 15 # number of generated test rows
874
+ end
875
+
876
+ it "should return only specified max rows" do
877
+ drill_through = @query.execute_drill_through(:max_rows => 10)
878
+ drill_through.rows.size.should == 10
879
+ end
880
+
881
+ it "should return only specified fields" do
882
+ @drill_through = @query.execute_drill_through(:return => [
883
+ '[Time].[Month]',
884
+ '[Product].[Product Family]',
885
+ '[Customers].[City]',
886
+ '[Measures].[Unit Sales]', '[Measures].[Store Sales]'
887
+ ])
888
+ @drill_through.column_labels.should == [
889
+ "Month",
890
+ "Product Family",
891
+ "City",
892
+ "Unit Sales", "Store Sales"
893
+ ]
894
+ end
895
+
896
+ end
897
+
898
+ end