mondrian-olap 0.4.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. data/.rspec +2 -0
  2. data/Changelog.md +60 -0
  3. data/Gemfile +21 -0
  4. data/LICENSE-Mondrian.html +259 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +302 -0
  7. data/RUNNING_TESTS.rdoc +66 -0
  8. data/Rakefile +48 -0
  9. data/VERSION +1 -0
  10. data/lib/mondrian-olap.rb +1 -0
  11. data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
  12. data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
  13. data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
  14. data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
  15. data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
  16. data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
  17. data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
  18. data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
  19. data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
  20. data/lib/mondrian/jars/javacup.jar +0 -0
  21. data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
  22. data/lib/mondrian/jars/log4j.properties +5 -0
  23. data/lib/mondrian/jars/mondrian.jar +0 -0
  24. data/lib/mondrian/jars/olap4j.jar +0 -0
  25. data/lib/mondrian/olap.rb +17 -0
  26. data/lib/mondrian/olap/connection.rb +201 -0
  27. data/lib/mondrian/olap/cube.rb +297 -0
  28. data/lib/mondrian/olap/error.rb +57 -0
  29. data/lib/mondrian/olap/query.rb +342 -0
  30. data/lib/mondrian/olap/result.rb +264 -0
  31. data/lib/mondrian/olap/schema.rb +378 -0
  32. data/lib/mondrian/olap/schema_element.rb +153 -0
  33. data/lib/mondrian/olap/schema_udf.rb +282 -0
  34. data/mondrian-olap.gemspec +128 -0
  35. data/spec/connection_role_spec.rb +130 -0
  36. data/spec/connection_spec.rb +72 -0
  37. data/spec/cube_spec.rb +318 -0
  38. data/spec/fixtures/MondrianTest.xml +134 -0
  39. data/spec/fixtures/MondrianTestOracle.xml +134 -0
  40. data/spec/mondrian_spec.rb +53 -0
  41. data/spec/query_spec.rb +807 -0
  42. data/spec/rake_tasks.rb +260 -0
  43. data/spec/schema_definition_spec.rb +1249 -0
  44. data/spec/spec_helper.rb +134 -0
  45. data/spec/support/matchers/be_like.rb +24 -0
  46. metadata +278 -0
@@ -0,0 +1,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