mondrian-olap 0.4.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.rspec +2 -0
  2. data/Changelog.md +60 -0
  3. data/Gemfile +21 -0
  4. data/LICENSE-Mondrian.html +259 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +302 -0
  7. data/RUNNING_TESTS.rdoc +66 -0
  8. data/Rakefile +48 -0
  9. data/VERSION +1 -0
  10. data/lib/mondrian-olap.rb +1 -0
  11. data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
  12. data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
  13. data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
  14. data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
  15. data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
  16. data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
  17. data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
  18. data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
  19. data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
  20. data/lib/mondrian/jars/javacup.jar +0 -0
  21. data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
  22. data/lib/mondrian/jars/log4j.properties +5 -0
  23. data/lib/mondrian/jars/mondrian.jar +0 -0
  24. data/lib/mondrian/jars/olap4j.jar +0 -0
  25. data/lib/mondrian/olap.rb +17 -0
  26. data/lib/mondrian/olap/connection.rb +201 -0
  27. data/lib/mondrian/olap/cube.rb +297 -0
  28. data/lib/mondrian/olap/error.rb +57 -0
  29. data/lib/mondrian/olap/query.rb +342 -0
  30. data/lib/mondrian/olap/result.rb +264 -0
  31. data/lib/mondrian/olap/schema.rb +378 -0
  32. data/lib/mondrian/olap/schema_element.rb +153 -0
  33. data/lib/mondrian/olap/schema_udf.rb +282 -0
  34. data/mondrian-olap.gemspec +128 -0
  35. data/spec/connection_role_spec.rb +130 -0
  36. data/spec/connection_spec.rb +72 -0
  37. data/spec/cube_spec.rb +318 -0
  38. data/spec/fixtures/MondrianTest.xml +134 -0
  39. data/spec/fixtures/MondrianTestOracle.xml +134 -0
  40. data/spec/mondrian_spec.rb +53 -0
  41. data/spec/query_spec.rb +807 -0
  42. data/spec/rake_tasks.rb +260 -0
  43. data/spec/schema_definition_spec.rb +1249 -0
  44. data/spec/spec_helper.rb +134 -0
  45. data/spec/support/matchers/be_like.rb +24 -0
  46. metadata +278 -0
@@ -0,0 +1,134 @@
1
+ <?xml version="1.0"?>
2
+ <Schema name="MondrianTest">
3
+ <Dimension name="Time" type="TimeDimension">
4
+ <Hierarchy hasAll="false" primaryKey="ID">
5
+ <Table name="TIME"/>
6
+ <Level name="Year" column="THE_YEAR" type="Numeric" uniqueMembers="true"
7
+ levelType="TimeYears"/>
8
+ <Level name="Quarter" column="QUARTER" uniqueMembers="false"
9
+ levelType="TimeQuarters"/>
10
+ <Level name="Month" column="MONTH_OF_YEAR" uniqueMembers="false" type="Numeric"
11
+ levelType="TimeMonths"/>
12
+ </Hierarchy>
13
+ <Hierarchy hasAll="true" name="Weekly" primaryKey="ID">
14
+ <Table name="TIME"/>
15
+ <Level name="Year" column="THE_YEAR" type="Numeric" uniqueMembers="true"
16
+ levelType="TimeYears"/>
17
+ <Level name="Week" column="WEEK_OF_YEAR" type="Numeric" uniqueMembers="false"
18
+ levelType="TimeWeeks"/>
19
+ <Level name="Day" column="DAY_OF_MONTH" uniqueMembers="false" type="Numeric"
20
+ levelType="TimeDays"/>
21
+ </Hierarchy>
22
+ </Dimension>
23
+
24
+ <Dimension name="Product">
25
+ <Hierarchy hasAll="true" primaryKey="ID" primaryKeyTable="PRODUCTS">
26
+ <Join leftKey="PRODUCT_CLASS_ID" rightKey="ID">
27
+ <Table name="PRODUCTS"/>
28
+ <Table name="PRODUCT_CLASSES"/>
29
+ </Join>
30
+ <Level name="Product Family" table="PRODUCT_CLASSES" column="PRODUCT_FAMILY"
31
+ uniqueMembers="true"/>
32
+ <Level name="Product Department" table="PRODUCT_CLASSES" column="PRODUCT_DEPARTMENT"
33
+ uniqueMembers="false"/>
34
+ <Level name="Product Category" table="PRODUCT_CLASSES" column="PRODUCT_CATEGORY"
35
+ uniqueMembers="false"/>
36
+ <Level name="Product Subcategory" table="PRODUCT_CLASSES" column="PRODUCT_SUBCATEGORY"
37
+ uniqueMembers="false"/>
38
+ <Level name="Brand Name" table="PRODUCTS" column="BRAND_NAME" uniqueMembers="false"/>
39
+ <Level name="Product Name" table="PRODUCTS" column="PRODUCT_NAME"
40
+ uniqueMembers="true"/>
41
+ </Hierarchy>
42
+ </Dimension>
43
+
44
+ <Cube name="Sales" defaultMeasure="Unit Sales">
45
+ <Table name="SALES"/>
46
+ <DimensionUsage name="Time" source="Time" foreignKey="TIME_ID"/>
47
+ <DimensionUsage name="Product" source="Product" foreignKey="PRODUCT_ID"/>
48
+ <Dimension name="Customers" foreignKey="CUSTOMER_ID">
49
+ <Hierarchy hasAll="true" allMemberName="All Customers" primaryKey="ID">
50
+ <Table name="CUSTOMERS"/>
51
+ <Level name="Country" column="COUNTRY" uniqueMembers="true"/>
52
+ <Level name="State Province" column="STATE_PROVINCE" uniqueMembers="true"/>
53
+ <Level name="City" column="CITY" uniqueMembers="false"/>
54
+ <Level name="Name" column="ID" type="Numeric" uniqueMembers="true">
55
+ <NameExpression>
56
+ <SQL dialect="oracle">
57
+ fname || ' ' || lname
58
+ </SQL>
59
+ <SQL dialect="postgres">
60
+ "fname" || ' ' || "lname"
61
+ </SQL>
62
+ <SQL dialect="mysql">
63
+ CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)
64
+ </SQL>
65
+ <SQL dialect="luciddb">
66
+ fname || ' ' || lname
67
+ </SQL>
68
+ <SQL dialect="generic">
69
+ FULLNAME
70
+ </SQL>
71
+ </NameExpression>
72
+ <OrdinalExpression>
73
+ <SQL dialect="oracle">
74
+ fname || ' ' || lname
75
+ </SQL>
76
+ <SQL dialect="postgres">
77
+ "fname" || ' ' || "lname"
78
+ </SQL>
79
+ <SQL dialect="mysql">
80
+ CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)
81
+ </SQL>
82
+ <SQL dialect="luciddb">
83
+ fname || ' ' || lname
84
+ </SQL>
85
+ <SQL dialect="generic">
86
+ FULLNAME
87
+ </SQL>
88
+ </OrdinalExpression>
89
+ <Property name="Gender" column="GENDER"/>
90
+ </Level>
91
+ </Hierarchy>
92
+ </Dimension>
93
+ <Dimension name="Gender" foreignKey="CUSTOMER_ID">
94
+ <Hierarchy hasAll="true" allMemberName="All Gender" primaryKey="ID">
95
+ <Table name="CUSTOMERS"/>
96
+ <Level name="Gender" column="GENDER" uniqueMembers="true"/>
97
+ </Hierarchy>
98
+ </Dimension>
99
+
100
+ <Measure name="Unit Sales" column="UNIT_SALES" aggregator="sum"
101
+ formatString="Standard"/>
102
+ <Measure name="Store Cost" column="STORE_COST" aggregator="sum"
103
+ formatString="#,###.00"/>
104
+ <Measure name="Store Sales" column="STORE_SALES" aggregator="sum"
105
+ formatString="#,###.00"/>
106
+ <Measure name="Sales Count" column="PRODUCT_ID" aggregator="count"
107
+ formatString="#,###"/>
108
+ <Measure name="Customer Count" column="CUSTOMER_ID"
109
+ aggregator="distinct-count" formatString="#,###"/>
110
+ <CalculatedMember
111
+ name="Profit"
112
+ dimension="Measures">
113
+ <Formula>[Measures].[Store Sales] - [Measures].[Store Cost]</Formula>
114
+ <CalculatedMemberProperty name="FORMAT_STRING" value="$#,##0.00"/>
115
+ </CalculatedMember>
116
+ <CalculatedMember
117
+ name="Profit last Period"
118
+ dimension="Measures"
119
+ formula="COALESCEEMPTY((Measures.[Profit], [Time].[Time].PREVMEMBER), Measures.[Profit])"
120
+ visible="false">
121
+ <CalculatedMemberProperty name="FORMAT_STRING" value="$#,##0.00"/>
122
+ <CalculatedMemberProperty name="MEMBER_ORDINAL" value="18"/>
123
+ </CalculatedMember>
124
+ <CalculatedMember
125
+ name="Profit Growth"
126
+ dimension="Measures"
127
+ formula="([Measures].[Profit] - [Measures].[Profit last Period]) / [Measures].[Profit last Period]"
128
+ visible="true"
129
+ caption="Gewinn-Wachstum">
130
+ <CalculatedMemberProperty name="FORMAT_STRING" value="0.0%"/>
131
+ </CalculatedMember>
132
+ </Cube>
133
+
134
+ </Schema>
@@ -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
@@ -0,0 +1,807 @@
1
+ require "spec_helper"
2
+
3
+ describe "Query" do
4
+ def quote_table_name(name)
5
+ ActiveRecord::Base.connection.quote_table_name(name)
6
+ end
7
+
8
+ before(:all) do
9
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
10
+ @sql = ActiveRecord::Base.connection
11
+
12
+ @query_string = <<-SQL
13
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
14
+ {[Product].children} ON ROWS
15
+ FROM [Sales]
16
+ WHERE ([Time].[2010].[Q1], [Customers].[USA].[CA])
17
+ SQL
18
+
19
+ @sql_select = <<-SQL
20
+ SELECT SUM(unit_sales) unit_sales_sum, SUM(store_sales) store_sales_sum
21
+ FROM sales
22
+ LEFT JOIN products ON sales.product_id = products.id
23
+ LEFT JOIN product_classes ON products.product_class_id = product_classes.id
24
+ LEFT JOIN #{quote_table_name('time')} ON sales.time_id = #{quote_table_name('time')}.id
25
+ LEFT JOIN customers ON sales.customer_id = customers.id
26
+ WHERE #{quote_table_name('time')}.the_year = 2010 AND #{quote_table_name('time')}.quarter = 'Q1'
27
+ AND customers.country = 'USA' AND customers.state_province = 'CA'
28
+ GROUP BY product_classes.product_family
29
+ ORDER BY product_classes.product_family
30
+ SQL
31
+
32
+ end
33
+
34
+ def sql_select_numbers(select_string)
35
+ @sql.select_rows(select_string).map do |rows|
36
+ rows.map{|col| BigDecimal(col.to_s)}
37
+ end
38
+ end
39
+
40
+ describe "result" do
41
+ before(:all) do
42
+
43
+ # TODO: replace hardcoded expected values with result of SQL query
44
+ @expected_column_names = ["Unit Sales", "Store Sales"]
45
+ @expected_column_full_names = ["[Measures].[Unit Sales]", "[Measures].[Store Sales]"]
46
+ @expected_drillable_columns = [false, false]
47
+ @expected_row_names = ["Drink", "Food", "Non-Consumable"]
48
+ @expected_row_full_names = ["[Product].[Drink]", "[Product].[Food]", "[Product].[Non-Consumable]"]
49
+ @expected_drillable_rows = [true, true, true]
50
+
51
+ # AR JDBC driver always returns strings, need to convert to BigDecimal
52
+ @expected_result_values = sql_select_numbers(@sql_select)
53
+
54
+ @expected_result_values_by_columns =
55
+ [@expected_result_values.map{|row| row[0]}, @expected_result_values.map{|row| row[1]}]
56
+
57
+ @result = @olap.execute @query_string
58
+ end
59
+
60
+ it "should return axes" do
61
+ @result.axes_count.should == 2
62
+ end
63
+
64
+ it "should return column names" do
65
+ @result.column_names.should == @expected_column_names
66
+ @result.column_full_names.should == @expected_column_full_names
67
+ end
68
+
69
+ it "should return row names" do
70
+ @result.row_names.should == @expected_row_names
71
+ @result.row_full_names.should == @expected_row_full_names
72
+ end
73
+
74
+ it "should return axis by index names" do
75
+ @result.axis_names[0].should == @expected_column_names
76
+ @result.axis_full_names[0].should == @expected_column_full_names
77
+ end
78
+
79
+ it "should return column members" do
80
+ @result.column_members.map(&:name).should == @expected_column_names
81
+ @result.column_members.map(&:full_name).should == @expected_column_full_names
82
+ @result.column_members.map(&:"drillable?").should == @expected_drillable_columns
83
+ end
84
+
85
+ it "should return row members" do
86
+ @result.row_members.map(&:name).should == @expected_row_names
87
+ @result.row_members.map(&:full_name).should == @expected_row_full_names
88
+ @result.row_members.map(&:"drillable?").should == @expected_drillable_rows
89
+ end
90
+
91
+ it "should return cells" do
92
+ @result.values.should == @expected_result_values
93
+ end
94
+
95
+ it "should return cells with specified axes number sequence" do
96
+ @result.values(0, 1).should == @expected_result_values_by_columns
97
+ end
98
+
99
+ it "should return cells with specified axes name sequence" do
100
+ @result.values(:columns, :rows).should == @expected_result_values_by_columns
101
+ end
102
+
103
+ it "should return formatted cells" do
104
+ @result.formatted_values.map{|r| r.map{|s| BigDecimal.new(s.gsub(',',''))}}.should == @expected_result_values
105
+ end
106
+
107
+ end
108
+
109
+ describe "builder" do
110
+
111
+ before(:each) do
112
+ @query = @olap.from('Sales')
113
+ end
114
+
115
+ describe "from cube" do
116
+ it "should return query" do
117
+ @query.should be_a(Mondrian::OLAP::Query)
118
+ @query.cube_name.should == 'Sales'
119
+ end
120
+ end
121
+
122
+ describe "columns" do
123
+ it "should accept list" do
124
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').should equal(@query)
125
+ @query.columns.should == ['[Measures].[Unit Sales]', '[Measures].[Store Sales]']
126
+ end
127
+
128
+ it "should accept list as array" do
129
+ @query.columns(['[Measures].[Unit Sales]', '[Measures].[Store Sales]'])
130
+ @query.columns.should == ['[Measures].[Unit Sales]', '[Measures].[Store Sales]']
131
+ end
132
+
133
+ it "should accept with several method calls" do
134
+ @query.columns('[Measures].[Unit Sales]').columns('[Measures].[Store Sales]')
135
+ @query.columns.should == ['[Measures].[Unit Sales]', '[Measures].[Store Sales]']
136
+ end
137
+ end
138
+
139
+ describe "other axis" do
140
+ it "should accept axis with index member list" do
141
+ @query.axis(0, '[Measures].[Unit Sales]', '[Measures].[Store Sales]')
142
+ @query.axis(0).should == ['[Measures].[Unit Sales]', '[Measures].[Store Sales]']
143
+ end
144
+
145
+ it "should accept rows list" do
146
+ @query.rows('[Product].children')
147
+ @query.rows.should == ['[Product].children']
148
+ end
149
+
150
+ it "should accept pages list" do
151
+ @query.pages('[Product].children')
152
+ @query.pages.should == ['[Product].children']
153
+ end
154
+ end
155
+
156
+ describe "crossjoin" do
157
+ it "should do crossjoin of several dimensions" do
158
+ @query.rows('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]')
159
+ @query.rows.should == [:crossjoin, ['[Product].children'], ['[Customers].[Canada]', '[Customers].[USA]']]
160
+ end
161
+
162
+ it "should do crossjoin passing array as first argument" do
163
+ @query.rows('[Product].children').crossjoin(['[Customers].[Canada]', '[Customers].[USA]'])
164
+ @query.rows.should == [:crossjoin, ['[Product].children'], ['[Customers].[Canada]', '[Customers].[USA]']]
165
+ end
166
+ end
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
+
175
+ describe "nonempty" do
176
+ it "should limit to set of members with nonempty values" do
177
+ @query.rows('[Product].children').nonempty
178
+ @query.rows.should == [:nonempty, ['[Product].children']]
179
+ end
180
+ end
181
+
182
+ describe "order" do
183
+ it "should order by one measure" do
184
+ @query.rows('[Product].children').order('[Measures].[Unit Sales]', :bdesc)
185
+ @query.rows.should == [:order, ['[Product].children'], '[Measures].[Unit Sales]', 'BDESC']
186
+ end
187
+
188
+ it "should order using String order direction" do
189
+ @query.rows('[Product].children').order('[Measures].[Unit Sales]', 'DESC')
190
+ @query.rows.should == [:order, ['[Product].children'], '[Measures].[Unit Sales]', 'DESC']
191
+ end
192
+
193
+ it "should order by measure and other member" do
194
+ @query.rows('[Product].children').order(['[Measures].[Unit Sales]', '[Customers].[USA]'], :basc)
195
+ @query.rows.should == [:order, ['[Product].children'], ['[Measures].[Unit Sales]', '[Customers].[USA]'], 'BASC']
196
+ end
197
+ end
198
+
199
+ %w(top bottom).each do |extreme|
200
+ describe extreme do
201
+ it "should select #{extreme} count rows by measure" do
202
+ @query.rows('[Product].children').send(:"#{extreme}_count", 5, '[Measures].[Unit Sales]')
203
+ @query.rows.should == [:"#{extreme}_count", ['[Product].children'], 5, '[Measures].[Unit Sales]']
204
+ end
205
+
206
+ it "should select #{extreme} count rows without measure" do
207
+ @query.rows('[Product].children').send(:"#{extreme}_count", 5)
208
+ @query.rows.should == [:"#{extreme}_count", ['[Product].children'], 5]
209
+ end
210
+
211
+ it "should select #{extreme} percent rows by measure" do
212
+ @query.rows('[Product].children').send(:"#{extreme}_percent", 20, '[Measures].[Unit Sales]')
213
+ @query.rows.should == [:"#{extreme}_percent", ['[Product].children'], 20, '[Measures].[Unit Sales]']
214
+ end
215
+
216
+ it "should select #{extreme} sum rows by measure" do
217
+ @query.rows('[Product].children').send(:"#{extreme}_sum", 1000, '[Measures].[Unit Sales]')
218
+ @query.rows.should == [:"#{extreme}_sum", ['[Product].children'], 1000, '[Measures].[Unit Sales]']
219
+ end
220
+ end
221
+ end
222
+
223
+ describe "hierarchize" do
224
+ it "should hierarchize simple set" do
225
+ @query.rows('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize
226
+ @query.rows.should == [:hierarchize, ['[Customers].[Country].Members', '[Customers].[City].Members']]
227
+ end
228
+
229
+ it "should hierarchize last set of crossjoin" do
230
+ @query.rows('[Product].children').crossjoin('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize
231
+ @query.rows.should == [:crossjoin, ['[Product].children'],
232
+ [:hierarchize, ['[Customers].[Country].Members', '[Customers].[City].Members']]]
233
+ end
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
+
241
+ it "should hierarchize all crossjoin" do
242
+ @query.rows('[Product].children').crossjoin('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize_all
243
+ @query.rows.should == [:hierarchize, [:crossjoin, ['[Product].children'],
244
+ ['[Customers].[Country].Members', '[Customers].[City].Members']]]
245
+ end
246
+
247
+ it "should hierarchize with POST" do
248
+ @query.rows('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize(:post)
249
+ @query.rows.should == [:hierarchize, ['[Customers].[Country].Members', '[Customers].[City].Members'], 'POST']
250
+ end
251
+
252
+ end
253
+
254
+ describe "except" do
255
+ it "should except one set from other" do
256
+ @query.rows('[Customers].[Country].Members').except('[Customers].[USA]')
257
+ @query.rows.should == [:except, ['[Customers].[Country].Members'], ['[Customers].[USA]']]
258
+ end
259
+
260
+ it "should except from last set of crossjoin" do
261
+ @query.rows('[Product].children').crossjoin('[Customers].[Country].Members').except('[Customers].[USA]')
262
+ @query.rows.should == [:crossjoin, ['[Product].children'],
263
+ [:except, ['[Customers].[Country].Members'], ['[Customers].[USA]']]]
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
271
+ end
272
+
273
+ describe "filter" do
274
+ it "should filter set by condition" do
275
+ @query.rows('[Customers].[Country].Members').filter('[Measures].[Unit Sales] > 1000')
276
+ @query.rows.should == [:filter, ['[Customers].[Country].Members'], '[Measures].[Unit Sales] > 1000']
277
+ end
278
+
279
+ it "should filter using set alias" do
280
+ @query.rows('[Customers].[Country].Members').filter('NOT ISEMPTY(S.CURRENT)', :as => 'S')
281
+ @query.rows.should == [:filter, ['[Customers].[Country].Members'], 'NOT ISEMPTY(S.CURRENT)', 'S']
282
+ end
283
+ end
284
+
285
+ describe "where" do
286
+ it "should accept conditions" do
287
+ @query.where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').should equal(@query)
288
+ @query.where.should == ['[Time].[2010].[Q1]', '[Customers].[USA].[CA]']
289
+ end
290
+
291
+ it "should accept conditions as array" do
292
+ @query.where(['[Time].[2010].[Q1]', '[Customers].[USA].[CA]'])
293
+ @query.where.should == ['[Time].[2010].[Q1]', '[Customers].[USA].[CA]']
294
+ end
295
+
296
+ it "should accept conditions with several method calls" do
297
+ @query.where('[Time].[2010].[Q1]').where('[Customers].[USA].[CA]')
298
+ @query.where.should == ['[Time].[2010].[Q1]', '[Customers].[USA].[CA]']
299
+ end
300
+
301
+ it "should do crossjoin of where conditions" do
302
+ @query.where('[Customers].[USA]').crossjoin('[Time].[2011].[Q1]', '[Time].[2011].[Q2]')
303
+ @query.where.should == [:crossjoin, ['[Customers].[USA]'], ['[Time].[2011].[Q1]', '[Time].[2011].[Q2]']]
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
310
+ end
311
+
312
+ describe "with member" do
313
+ it "should accept definition" do
314
+ @query.with_member('[Measures].[ProfitPct]').
315
+ as('Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])').
316
+ should equal(@query)
317
+ @query.with.should == [
318
+ [ :member, '[Measures].[ProfitPct]',
319
+ 'Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])'
320
+ ]
321
+ ]
322
+ end
323
+
324
+ it "should accept definition with additional parameters" do
325
+ @query.with_member('[Measures].[ProfitPct]').
326
+ as('Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
327
+ :solve_order => 1,
328
+ :format_string => 'Percent')
329
+ @query.with.should == [
330
+ [ :member, '[Measures].[ProfitPct]',
331
+ 'Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
332
+ {:solve_order => 1, :format_string => 'Percent'}
333
+ ]
334
+ ]
335
+ end
336
+ end
337
+
338
+ describe "with set" do
339
+ it "should accept simple defition" do
340
+ @query.with_set('SelectedRows').as('[Product].children')
341
+ @query.with.should == [
342
+ [ :set, 'SelectedRows',
343
+ ['[Product].children']
344
+ ]
345
+ ]
346
+ end
347
+
348
+ it "should accept definition with crossjoin" do
349
+ @query.with_set('SelectedRows').as('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]')
350
+ @query.with.should == [
351
+ [ :set, 'SelectedRows',
352
+ [:crossjoin, ['[Product].children'], ['[Customers].[Canada]', '[Customers].[USA]']]
353
+ ]
354
+ ]
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
365
+ end
366
+
367
+ describe "to MDX" do
368
+ it "should return MDX query" do
369
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
370
+ rows('[Product].children').
371
+ where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
372
+ to_mdx.should be_like <<-SQL
373
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
374
+ [Product].children ON ROWS
375
+ FROM [Sales]
376
+ WHERE ([Time].[2010].[Q1], [Customers].[USA].[CA])
377
+ SQL
378
+ end
379
+
380
+ it "should return query with crossjoin" do
381
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
382
+ rows('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
383
+ where('[Time].[2010].[Q1]').
384
+ to_mdx.should be_like <<-SQL
385
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
386
+ CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]}) ON ROWS
387
+ FROM [Sales]
388
+ WHERE ([Time].[2010].[Q1])
389
+ SQL
390
+ end
391
+
392
+ it "should return query with several crossjoins" do
393
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
394
+ rows('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
395
+ crossjoin('[Time].[2010].[Q1]', '[Time].[2010].[Q2]').
396
+ to_mdx.should be_like <<-SQL
397
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
398
+ CROSSJOIN(CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]}),
399
+ {[Time].[2010].[Q1], [Time].[2010].[Q2]}) ON ROWS
400
+ FROM [Sales]
401
+ SQL
402
+ end
403
+
404
+ it "should return query with crossjoin and nonempty" do
405
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
406
+ rows('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').nonempty.
407
+ where('[Time].[2010].[Q1]').
408
+ to_mdx.should be_like <<-SQL
409
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
410
+ NON EMPTY CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]}) ON ROWS
411
+ FROM [Sales]
412
+ WHERE ([Time].[2010].[Q1])
413
+ SQL
414
+ end
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
+
428
+ it "should return query with where with several same dimension members" do
429
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
430
+ rows('[Product].children').
431
+ where('[Customers].[Canada]', '[Customers].[USA]').
432
+ to_mdx.should be_like <<-SQL
433
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
434
+ [Product].children ON ROWS
435
+ FROM [Sales]
436
+ WHERE {[Customers].[Canada], [Customers].[USA]}
437
+ SQL
438
+ end
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
+
452
+ it "should return query with where with crossjoin" do
453
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
454
+ rows('[Product].children').
455
+ where('[Customers].[USA]').crossjoin('[Time].[2011].[Q1]', '[Time].[2011].[Q2]').
456
+ to_mdx.should be_like <<-SQL
457
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
458
+ [Product].children ON ROWS
459
+ FROM [Sales]
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]})
473
+ SQL
474
+ end
475
+
476
+ it "should return query with order by one measure" do
477
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
478
+ rows('[Product].children').order('[Measures].[Unit Sales]', :bdesc).
479
+ to_mdx.should be_like <<-SQL
480
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
481
+ ORDER([Product].children, [Measures].[Unit Sales], BDESC) ON ROWS
482
+ FROM [Sales]
483
+ SQL
484
+ end
485
+
486
+ it "should return query with order by measure and other member" do
487
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
488
+ rows('[Product].children').order(['[Measures].[Unit Sales]', '[Customers].[USA]'], :asc).
489
+ to_mdx.should be_like <<-SQL
490
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
491
+ ORDER([Product].children, ([Measures].[Unit Sales], [Customers].[USA]), ASC) ON ROWS
492
+ FROM [Sales]
493
+ SQL
494
+ end
495
+
496
+ %w(top bottom).each do |extreme|
497
+ it "should return query with #{extreme} count by one measure" do
498
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
499
+ rows('[Product].children').send(:"#{extreme}_count", 5, '[Measures].[Unit Sales]').
500
+ to_mdx.should be_like <<-SQL
501
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
502
+ #{extreme.upcase}COUNT([Product].children, 5, [Measures].[Unit Sales]) ON ROWS
503
+ FROM [Sales]
504
+ SQL
505
+ end
506
+
507
+ it "should return query with #{extreme} count without measure" do
508
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
509
+ rows('[Product].children').send(:"#{extreme}_count", 5).
510
+ to_mdx.should be_like <<-SQL
511
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
512
+ #{extreme.upcase}COUNT([Product].children, 5) ON ROWS
513
+ FROM [Sales]
514
+ SQL
515
+ end
516
+
517
+ it "should return query with #{extreme} count by measure and other member" do
518
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
519
+ rows('[Product].children').send(:"#{extreme}_count", 5, ['[Measures].[Unit Sales]', '[Customers].[USA]']).
520
+ to_mdx.should be_like <<-SQL
521
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
522
+ #{extreme.upcase}COUNT([Product].children, 5, ([Measures].[Unit Sales], [Customers].[USA])) ON ROWS
523
+ FROM [Sales]
524
+ SQL
525
+ end
526
+
527
+ it "should return query with #{extreme} percent by one measure" do
528
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
529
+ rows('[Product].children').send(:"#{extreme}_percent", 20, '[Measures].[Unit Sales]').
530
+ to_mdx.should be_like <<-SQL
531
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
532
+ #{extreme.upcase}PERCENT([Product].children, 20, [Measures].[Unit Sales]) ON ROWS
533
+ FROM [Sales]
534
+ SQL
535
+ end
536
+
537
+ it "should return query with #{extreme} sum by one measure" do
538
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
539
+ rows('[Product].children').send(:"#{extreme}_sum", 1000, '[Measures].[Unit Sales]').
540
+ to_mdx.should be_like <<-SQL
541
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
542
+ #{extreme.upcase}SUM([Product].children, 1000, [Measures].[Unit Sales]) ON ROWS
543
+ FROM [Sales]
544
+ SQL
545
+ end
546
+ end
547
+
548
+ it "should return query with hierarchize" do
549
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
550
+ rows('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize.
551
+ to_mdx.should be_like <<-SQL
552
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
553
+ HIERARCHIZE({[Customers].[Country].Members, [Customers].[City].Members}) ON ROWS
554
+ FROM [Sales]
555
+ SQL
556
+ end
557
+
558
+ it "should return query with hierarchize and order" do
559
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
560
+ rows('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize(:post).
561
+ to_mdx.should be_like <<-SQL
562
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
563
+ HIERARCHIZE({[Customers].[Country].Members, [Customers].[City].Members}, POST) ON ROWS
564
+ FROM [Sales]
565
+ SQL
566
+ end
567
+
568
+ it "should return query with except" do
569
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
570
+ rows('[Customers].[Country].Members').except('[Customers].[USA]').
571
+ to_mdx.should be_like <<-SQL
572
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
573
+ EXCEPT([Customers].[Country].Members, {[Customers].[USA]}) ON ROWS
574
+ FROM [Sales]
575
+ SQL
576
+ end
577
+
578
+ it "should return query with filter" do
579
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
580
+ rows('[Customers].[Country].Members').filter('[Measures].[Unit Sales] > 1000').
581
+ to_mdx.should be_like <<-SQL
582
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
583
+ FILTER([Customers].[Country].Members, [Measures].[Unit Sales] > 1000) ON ROWS
584
+ FROM [Sales]
585
+ SQL
586
+ end
587
+
588
+ it "should return query with filter and set alias" do
589
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
590
+ rows('[Customers].[Country].Members').filter('NOT ISEMPTY(S.CURRENT)', :as => 'S').
591
+ to_mdx.should be_like <<-SQL
592
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
593
+ FILTER([Customers].[Country].Members AS S, NOT ISEMPTY(S.CURRENT)) ON ROWS
594
+ FROM [Sales]
595
+ SQL
596
+ end
597
+
598
+ it "should return query with filter non-empty" do
599
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
600
+ rows('[Customers].[Country].Members').filter_nonempty.
601
+ to_mdx.should be_like <<-SQL
602
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
603
+ FILTER([Customers].[Country].Members AS S, NOT ISEMPTY(S.CURRENT)) ON ROWS
604
+ FROM [Sales]
605
+ SQL
606
+ end
607
+
608
+ it "should return query including WITH MEMBER clause" do
609
+ @query.
610
+ with_member('[Measures].[ProfitPct]').
611
+ as('Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
612
+ :solve_order => 1, :format_string => 'Percent').
613
+ with_member('[Measures].[ProfitValue]').
614
+ as('[Measures].[Store Sales] * [Measures].[ProfitPct]',
615
+ :solve_order => 2, :format_string => 'Currency').
616
+ columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
617
+ rows('[Product].children').
618
+ where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
619
+ to_mdx.should be_like <<-SQL
620
+ WITH
621
+ MEMBER [Measures].[ProfitPct] AS
622
+ 'Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
623
+ SOLVE_ORDER = 1, FORMAT_STRING = 'Percent'
624
+ MEMBER [Measures].[ProfitValue] AS
625
+ '[Measures].[Store Sales] * [Measures].[ProfitPct]',
626
+ SOLVE_ORDER = 2, FORMAT_STRING = 'Currency'
627
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
628
+ [Product].children ON ROWS
629
+ FROM [Sales]
630
+ WHERE ([Time].[2010].[Q1], [Customers].[USA].[CA])
631
+ SQL
632
+ end
633
+
634
+ it "should return query including WITH SET clause" do
635
+ @query.with_set('SelectedRows').
636
+ as('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
637
+ with_member('[Measures].[Profit]').
638
+ as('[Measures].[Store Sales] - [Measures].[Store Cost]').
639
+ columns('[Measures].[Profit]').
640
+ rows('SelectedRows').
641
+ to_mdx.should be_like <<-SQL
642
+ WITH
643
+ SET SelectedRows AS
644
+ 'CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]})'
645
+ MEMBER [Measures].[Profit] AS
646
+ '[Measures].[Store Sales] - [Measures].[Store Cost]'
647
+ SELECT {[Measures].[Profit]} ON COLUMNS,
648
+ SelectedRows ON ROWS
649
+ FROM [Sales]
650
+ SQL
651
+ end
652
+ end
653
+
654
+ describe "execute" do
655
+ it "should return result" do
656
+ result = @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
657
+ rows('[Product].children').
658
+ where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
659
+ execute
660
+ result.values.should == sql_select_numbers(@sql_select)
661
+ end
662
+ end
663
+
664
+ describe "result HTML formatting" do
665
+ it "should format result" do
666
+ result = @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
667
+ rows('[Product].children').
668
+ where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
669
+ execute
670
+ Nokogiri::HTML.fragment(result.to_html).css('tr').size.should == (sql_select_numbers(@sql_select).size + 1)
671
+ end
672
+
673
+ # it "test" do
674
+ # puts @olap.from('Sales').
675
+ # columns('[Product].children').
676
+ # rows('[Customers].[USA].[CA].children').
677
+ # where('[Time].[2010].[Q1]', '[Measures].[Store Sales]').
678
+ # execute.to_html
679
+ # end
680
+ end
681
+
682
+ end
683
+
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" 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
+ else
790
+ [
791
+ Fixnum, String, Fixnum, Fixnum, Fixnum,
792
+ String, String, String, String, String, String,
793
+ String, String, String, Fixnum,
794
+ String,
795
+ BigDecimal
796
+ ]
797
+ end
798
+ end
799
+
800
+ it "should return only specified max rows" do
801
+ drill_through = @result.drill_through(:row => 0, :column => 0, :max_rows => 10)
802
+ drill_through.rows.size.should == 10
803
+ end
804
+ end
805
+
806
+
807
+ end