mondrian-olap 0.3.0 → 0.5.0
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.
- 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
|