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.
- checksums.yaml +7 -0
- data/Changelog.md +38 -0
- data/LICENSE.txt +1 -1
- data/README.md +302 -0
- data/VERSION +1 -1
- data/lib/mondrian/jars/commons-collections-3.2.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.1.1.jar +0 -0
- data/lib/mondrian/jars/commons-math-1.1.jar +0 -0
- data/lib/mondrian/jars/eigenbase-properties-1.1.2.jar +0 -0
- data/lib/mondrian/jars/eigenbase-resgen-1.3.1.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom-1.3.1.jar +0 -0
- data/lib/mondrian/jars/{javacup.jar → javacup-10k.jar} +0 -0
- data/lib/mondrian/jars/log4j-1.2.14.jar +0 -0
- data/lib/mondrian/jars/mondrian.jar +0 -0
- data/lib/mondrian/jars/olap4j-1.0.1.539.jar +0 -0
- data/lib/mondrian/olap.rb +2 -1
- data/lib/mondrian/olap/connection.rb +163 -32
- data/lib/mondrian/olap/cube.rb +163 -24
- data/lib/mondrian/olap/error.rb +57 -0
- data/lib/mondrian/olap/query.rb +52 -17
- data/lib/mondrian/olap/result.rb +298 -6
- data/lib/mondrian/olap/schema.rb +220 -29
- data/lib/mondrian/olap/schema_element.rb +31 -11
- data/lib/mondrian/olap/schema_udf.rb +331 -0
- data/lib/mondrian/olap/version.rb +5 -0
- data/spec/connection_role_spec.rb +130 -0
- data/spec/connection_spec.rb +36 -1
- data/spec/cube_spec.rb +137 -7
- data/spec/fixtures/MondrianTest.xml +4 -4
- data/spec/mondrian_spec.rb +53 -0
- data/spec/query_spec.rb +294 -11
- data/spec/rake_tasks.rb +8 -8
- data/spec/schema_definition_spec.rb +845 -26
- data/spec/spec_helper.rb +26 -17
- data/spec/support/matchers/be_like.rb +2 -2
- metadata +296 -237
- data/.rspec +0 -2
- data/Gemfile +0 -18
- data/README.rdoc +0 -221
- data/RUNNING_TESTS.rdoc +0 -66
- data/Rakefile +0 -46
- data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
- data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
- data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
- data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
- data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
- data/lib/mondrian/jars/olap4j.jar +0 -0
- data/mondrian-olap.gemspec +0 -126
data/spec/connection_spec.rb
CHANGED
@@ -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
|
data/spec/cube_spec.rb
CHANGED
@@ -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 "
|
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="
|
59
|
+
<SQL dialect="postgresql">
|
60
60
|
"fname" || ' ' || "lname"
|
61
61
|
</SQL>
|
62
62
|
<SQL dialect="mysql">
|
63
|
-
CONCAT(`
|
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="
|
76
|
+
<SQL dialect="postgresql">
|
77
77
|
"fname" || ' ' || "lname"
|
78
78
|
</SQL>
|
79
79
|
<SQL dialect="mysql">
|
80
|
-
CONCAT(`
|
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
|
data/spec/query_spec.rb
CHANGED
@@ -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, :
|
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,
|
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
|
-
|
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
|