mondrian-olap 0.1.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.
Files changed (41) hide show
  1. data/.rspec +2 -0
  2. data/Gemfile +15 -0
  3. data/LICENSE-Mondrian.html +259 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.rdoc +219 -0
  6. data/RUNNING_TESTS.rdoc +41 -0
  7. data/Rakefile +46 -0
  8. data/VERSION +1 -0
  9. data/lib/mondrian-olap.rb +1 -0
  10. data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
  11. data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
  12. data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
  13. data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
  14. data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
  15. data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
  16. data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
  17. data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
  18. data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
  19. data/lib/mondrian/jars/javacup.jar +0 -0
  20. data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
  21. data/lib/mondrian/jars/log4j.properties +18 -0
  22. data/lib/mondrian/jars/mondrian.jar +0 -0
  23. data/lib/mondrian/jars/olap4j.jar +0 -0
  24. data/lib/mondrian/olap.rb +14 -0
  25. data/lib/mondrian/olap/connection.rb +122 -0
  26. data/lib/mondrian/olap/cube.rb +236 -0
  27. data/lib/mondrian/olap/query.rb +313 -0
  28. data/lib/mondrian/olap/result.rb +155 -0
  29. data/lib/mondrian/olap/schema.rb +158 -0
  30. data/lib/mondrian/olap/schema_element.rb +123 -0
  31. data/mondrian-olap.gemspec +116 -0
  32. data/spec/connection_spec.rb +56 -0
  33. data/spec/cube_spec.rb +259 -0
  34. data/spec/fixtures/MondrianTest.xml +128 -0
  35. data/spec/fixtures/MondrianTestOracle.xml +128 -0
  36. data/spec/query_spec.rb +582 -0
  37. data/spec/rake_tasks.rb +185 -0
  38. data/spec/schema_definition_spec.rb +345 -0
  39. data/spec/spec_helper.rb +67 -0
  40. data/spec/support/matchers/be_like.rb +24 -0
  41. metadata +217 -0
@@ -0,0 +1,582 @@
1
+ require "spec_helper"
2
+
3
+ describe "Query" do
4
+ before(:all) do
5
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
6
+ @sql = ActiveRecord::Base.connection
7
+
8
+ @query_string = <<-SQL
9
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
10
+ {[Product].children} ON ROWS
11
+ FROM [Sales]
12
+ WHERE ([Time].[2010].[Q1], [Customers].[USA].[CA])
13
+ SQL
14
+
15
+ @sql_select = <<-SQL
16
+ SELECT SUM(unit_sales) unit_sales_sum, SUM(store_sales) store_sales_sum
17
+ FROM sales
18
+ LEFT JOIN products ON sales.product_id = products.id
19
+ LEFT JOIN product_classes ON products.product_class_id = product_classes.id
20
+ LEFT JOIN time ON sales.time_id = time.id
21
+ LEFT JOIN customers ON sales.customer_id = customers.id
22
+ WHERE time.the_year = 2010 AND time.quarter = 'Q1'
23
+ AND customers.country = 'USA' AND customers.state_province = 'CA'
24
+ GROUP BY product_classes.product_family
25
+ ORDER BY product_classes.product_family
26
+ SQL
27
+
28
+ end
29
+
30
+ def sql_select_numbers(select_string)
31
+ @sql.select_rows(select_string).map do |rows|
32
+ rows.map{|col| BigDecimal(col.to_s)}
33
+ end
34
+ end
35
+
36
+ describe "result" do
37
+ before(:all) do
38
+
39
+ # TODO: replace hardcoded expected values with result of SQL query
40
+ @expected_column_names = ["Unit Sales", "Store Sales"]
41
+ @expected_column_full_names = ["[Measures].[Unit Sales]", "[Measures].[Store Sales]"]
42
+ @expected_drillable_columns = [false, false]
43
+ @expected_row_names = ["Drink", "Food", "Non-Consumable"]
44
+ @expected_row_full_names = ["[Product].[Drink]", "[Product].[Food]", "[Product].[Non-Consumable]"]
45
+ @expected_drillable_rows = [true, true, true]
46
+
47
+ # AR JDBC driver always returns strings, need to convert to BigDecimal
48
+ @expected_result_values = sql_select_numbers(@sql_select)
49
+
50
+ @expected_result_values_by_columns =
51
+ [@expected_result_values.map{|row| row[0]}, @expected_result_values.map{|row| row[1]}]
52
+
53
+ @result = @olap.execute @query_string
54
+ end
55
+
56
+ it "should return axes" do
57
+ @result.axes_count.should == 2
58
+ end
59
+
60
+ it "should return column names" do
61
+ @result.column_names.should == @expected_column_names
62
+ @result.column_full_names.should == @expected_column_full_names
63
+ end
64
+
65
+ it "should return row names" do
66
+ @result.row_names.should == @expected_row_names
67
+ @result.row_full_names.should == @expected_row_full_names
68
+ end
69
+
70
+ it "should return axis by index names" do
71
+ @result.axis_names[0].should == @expected_column_names
72
+ @result.axis_full_names[0].should == @expected_column_full_names
73
+ end
74
+
75
+ it "should return column members" do
76
+ @result.column_members.map(&:name).should == @expected_column_names
77
+ @result.column_members.map(&:full_name).should == @expected_column_full_names
78
+ @result.column_members.map(&:"drillable?").should == @expected_drillable_columns
79
+ end
80
+
81
+ it "should return row members" do
82
+ @result.row_members.map(&:name).should == @expected_row_names
83
+ @result.row_members.map(&:full_name).should == @expected_row_full_names
84
+ @result.row_members.map(&:"drillable?").should == @expected_drillable_rows
85
+ end
86
+
87
+ it "should return cells" do
88
+ @result.values.should == @expected_result_values
89
+ end
90
+
91
+ it "should return cells with specified axes number sequence" do
92
+ @result.values(0, 1).should == @expected_result_values_by_columns
93
+ end
94
+
95
+ it "should return cells with specified axes name sequence" do
96
+ @result.values(:columns, :rows).should == @expected_result_values_by_columns
97
+ end
98
+
99
+ it "should return formatted cells" do
100
+ @result.formatted_values.map{|r| r.map{|s| BigDecimal.new(s.gsub(',',''))}}.should == @expected_result_values
101
+ end
102
+
103
+ end
104
+
105
+ describe "builder" do
106
+
107
+ before(:each) do
108
+ @query = @olap.from('Sales')
109
+ end
110
+
111
+ describe "from cube" do
112
+ it "should return query" do
113
+ @query.should be_a(Mondrian::OLAP::Query)
114
+ @query.cube_name.should == 'Sales'
115
+ end
116
+ end
117
+
118
+ describe "columns" do
119
+ it "should accept list" do
120
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').should equal(@query)
121
+ @query.columns.should == ['[Measures].[Unit Sales]', '[Measures].[Store Sales]']
122
+ end
123
+
124
+ it "should accept list as array" do
125
+ @query.columns(['[Measures].[Unit Sales]', '[Measures].[Store Sales]'])
126
+ @query.columns.should == ['[Measures].[Unit Sales]', '[Measures].[Store Sales]']
127
+ end
128
+
129
+ it "should accept with several method calls" do
130
+ @query.columns('[Measures].[Unit Sales]').columns('[Measures].[Store Sales]')
131
+ @query.columns.should == ['[Measures].[Unit Sales]', '[Measures].[Store Sales]']
132
+ end
133
+ end
134
+
135
+ describe "other axis" do
136
+ it "should accept axis with index member list" do
137
+ @query.axis(0, '[Measures].[Unit Sales]', '[Measures].[Store Sales]')
138
+ @query.axis(0).should == ['[Measures].[Unit Sales]', '[Measures].[Store Sales]']
139
+ end
140
+
141
+ it "should accept rows list" do
142
+ @query.rows('[Product].children')
143
+ @query.rows.should == ['[Product].children']
144
+ end
145
+
146
+ it "should accept pages list" do
147
+ @query.pages('[Product].children')
148
+ @query.pages.should == ['[Product].children']
149
+ end
150
+ end
151
+
152
+ describe "crossjoin" do
153
+ it "should do crossjoin of several dimensions" do
154
+ @query.rows('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]')
155
+ @query.rows.should == [:crossjoin, ['[Product].children'], ['[Customers].[Canada]', '[Customers].[USA]']]
156
+ end
157
+
158
+ it "should do crossjoin passing array as first argument" do
159
+ @query.rows('[Product].children').crossjoin(['[Customers].[Canada]', '[Customers].[USA]'])
160
+ @query.rows.should == [:crossjoin, ['[Product].children'], ['[Customers].[Canada]', '[Customers].[USA]']]
161
+ end
162
+ end
163
+
164
+ describe "nonempty" do
165
+ it "should limit to set of members with nonempty values" do
166
+ @query.rows('[Product].children').nonempty
167
+ @query.rows.should == [:nonempty, ['[Product].children']]
168
+ end
169
+ end
170
+
171
+ describe "order" do
172
+ it "should order by one measure" do
173
+ @query.rows('[Product].children').order('[Measures].[Unit Sales]', :bdesc)
174
+ @query.rows.should == [:order, ['[Product].children'], '[Measures].[Unit Sales]', 'BDESC']
175
+ end
176
+
177
+ it "should order using String order direction" do
178
+ @query.rows('[Product].children').order('[Measures].[Unit Sales]', 'DESC')
179
+ @query.rows.should == [:order, ['[Product].children'], '[Measures].[Unit Sales]', 'DESC']
180
+ end
181
+
182
+ it "should order by measure and other member" do
183
+ @query.rows('[Product].children').order(['[Measures].[Unit Sales]', '[Customers].[USA]'], :basc)
184
+ @query.rows.should == [:order, ['[Product].children'], ['[Measures].[Unit Sales]', '[Customers].[USA]'], 'BASC']
185
+ end
186
+ end
187
+
188
+ %w(top bottom).each do |extreme|
189
+ describe extreme do
190
+ it "should select #{extreme} count rows by measure" do
191
+ @query.rows('[Product].children').send(:"#{extreme}_count", 5, '[Measures].[Unit Sales]')
192
+ @query.rows.should == [:"#{extreme}_count", ['[Product].children'], 5, '[Measures].[Unit Sales]']
193
+ end
194
+
195
+ it "should select #{extreme} count rows without measure" do
196
+ @query.rows('[Product].children').send(:"#{extreme}_count", 5)
197
+ @query.rows.should == [:"#{extreme}_count", ['[Product].children'], 5]
198
+ end
199
+
200
+ it "should select #{extreme} percent rows by measure" do
201
+ @query.rows('[Product].children').send(:"#{extreme}_percent", 20, '[Measures].[Unit Sales]')
202
+ @query.rows.should == [:"#{extreme}_percent", ['[Product].children'], 20, '[Measures].[Unit Sales]']
203
+ end
204
+
205
+ it "should select #{extreme} sum rows by measure" do
206
+ @query.rows('[Product].children').send(:"#{extreme}_sum", 1000, '[Measures].[Unit Sales]')
207
+ @query.rows.should == [:"#{extreme}_sum", ['[Product].children'], 1000, '[Measures].[Unit Sales]']
208
+ end
209
+ end
210
+ end
211
+
212
+ describe "hierarchize" do
213
+ it "should hierarchize simple set" do
214
+ @query.rows('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize
215
+ @query.rows.should == [:hierarchize, ['[Customers].[Country].Members', '[Customers].[City].Members']]
216
+ end
217
+
218
+ it "should hierarchize last set of crossjoin" do
219
+ @query.rows('[Product].children').crossjoin('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize
220
+ @query.rows.should == [:crossjoin, ['[Product].children'],
221
+ [:hierarchize, ['[Customers].[Country].Members', '[Customers].[City].Members']]]
222
+ end
223
+
224
+ it "should hierarchize all crossjoin" do
225
+ @query.rows('[Product].children').crossjoin('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize_all
226
+ @query.rows.should == [:hierarchize, [:crossjoin, ['[Product].children'],
227
+ ['[Customers].[Country].Members', '[Customers].[City].Members']]]
228
+ end
229
+
230
+ it "should hierarchize with POST" do
231
+ @query.rows('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize(:post)
232
+ @query.rows.should == [:hierarchize, ['[Customers].[Country].Members', '[Customers].[City].Members'], 'POST']
233
+ end
234
+
235
+ end
236
+
237
+ describe "except" do
238
+ it "should except one set from other" do
239
+ @query.rows('[Customers].[Country].Members').except('[Customers].[USA]')
240
+ @query.rows.should == [:except, ['[Customers].[Country].Members'], ['[Customers].[USA]']]
241
+ end
242
+
243
+ it "should except from last set of crossjoin" do
244
+ @query.rows('[Product].children').crossjoin('[Customers].[Country].Members').except('[Customers].[USA]')
245
+ @query.rows.should == [:crossjoin, ['[Product].children'],
246
+ [:except, ['[Customers].[Country].Members'], ['[Customers].[USA]']]]
247
+ end
248
+ end
249
+
250
+ describe "filter" do
251
+ it "should filter set by condition" do
252
+ @query.rows('[Customers].[Country].Members').filter('[Measures].[Unit Sales] > 1000')
253
+ @query.rows.should == [:filter, ['[Customers].[Country].Members'], '[Measures].[Unit Sales] > 1000']
254
+ end
255
+
256
+ it "should filter using set alias" do
257
+ @query.rows('[Customers].[Country].Members').filter('NOT ISEMPTY(S.CURRENT)', :as => 'S')
258
+ @query.rows.should == [:filter, ['[Customers].[Country].Members'], 'NOT ISEMPTY(S.CURRENT)', 'S']
259
+ end
260
+ end
261
+
262
+ describe "where" do
263
+ it "should accept conditions" do
264
+ @query.where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').should equal(@query)
265
+ @query.where.should == ['[Time].[2010].[Q1]', '[Customers].[USA].[CA]']
266
+ end
267
+
268
+ it "should accept conditions as array" do
269
+ @query.where(['[Time].[2010].[Q1]', '[Customers].[USA].[CA]'])
270
+ @query.where.should == ['[Time].[2010].[Q1]', '[Customers].[USA].[CA]']
271
+ end
272
+
273
+ it "should accept conditions with several method calls" do
274
+ @query.where('[Time].[2010].[Q1]').where('[Customers].[USA].[CA]')
275
+ @query.where.should == ['[Time].[2010].[Q1]', '[Customers].[USA].[CA]']
276
+ end
277
+ end
278
+
279
+ describe "with member" do
280
+ it "should accept definition" do
281
+ @query.with_member('[Measures].[ProfitPct]').
282
+ as('Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])').
283
+ should equal(@query)
284
+ @query.with.should == [
285
+ [ :member, '[Measures].[ProfitPct]',
286
+ 'Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])'
287
+ ]
288
+ ]
289
+ end
290
+
291
+ it "should accept definition with additional parameters" do
292
+ @query.with_member('[Measures].[ProfitPct]').
293
+ as('Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
294
+ :solve_order => 1,
295
+ :format_string => 'Percent')
296
+ @query.with.should == [
297
+ [ :member, '[Measures].[ProfitPct]',
298
+ 'Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
299
+ {:solve_order => 1, :format_string => 'Percent'}
300
+ ]
301
+ ]
302
+ end
303
+ end
304
+
305
+ describe "with set" do
306
+ it "should accept simple defition" do
307
+ @query.with_set('SelectedRows').as('[Product].children')
308
+ @query.with.should == [
309
+ [ :set, 'SelectedRows',
310
+ ['[Product].children']
311
+ ]
312
+ ]
313
+ end
314
+
315
+ it "should accept definition with crossjoin" do
316
+ @query.with_set('SelectedRows').as('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]')
317
+ @query.with.should == [
318
+ [ :set, 'SelectedRows',
319
+ [:crossjoin, ['[Product].children'], ['[Customers].[Canada]', '[Customers].[USA]']]
320
+ ]
321
+ ]
322
+ end
323
+ end
324
+
325
+ describe "to MDX" do
326
+ it "should return MDX query" do
327
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
328
+ rows('[Product].children').
329
+ where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
330
+ to_mdx.should be_like <<-SQL
331
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
332
+ [Product].children ON ROWS
333
+ FROM [Sales]
334
+ WHERE ([Time].[2010].[Q1], [Customers].[USA].[CA])
335
+ SQL
336
+ end
337
+
338
+ it "should return query with crossjoin" do
339
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
340
+ rows('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
341
+ where('[Time].[2010].[Q1]').
342
+ to_mdx.should be_like <<-SQL
343
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
344
+ CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]}) ON ROWS
345
+ FROM [Sales]
346
+ WHERE ([Time].[2010].[Q1])
347
+ SQL
348
+ end
349
+
350
+ it "should return query with several crossjoins" do
351
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
352
+ rows('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
353
+ crossjoin('[Time].[2010].[Q1]', '[Time].[2010].[Q2]').
354
+ to_mdx.should be_like <<-SQL
355
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
356
+ CROSSJOIN(CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]}),
357
+ {[Time].[2010].[Q1], [Time].[2010].[Q2]}) ON ROWS
358
+ FROM [Sales]
359
+ SQL
360
+ end
361
+
362
+ it "should return query with crossjoin and nonempty" do
363
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
364
+ rows('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').nonempty.
365
+ where('[Time].[2010].[Q1]').
366
+ to_mdx.should be_like <<-SQL
367
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
368
+ NON EMPTY CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]}) ON ROWS
369
+ FROM [Sales]
370
+ WHERE ([Time].[2010].[Q1])
371
+ SQL
372
+ end
373
+
374
+ it "should return query with order by one measure" do
375
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
376
+ rows('[Product].children').order('[Measures].[Unit Sales]', :bdesc).
377
+ to_mdx.should be_like <<-SQL
378
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
379
+ ORDER([Product].children, [Measures].[Unit Sales], BDESC) ON ROWS
380
+ FROM [Sales]
381
+ SQL
382
+ end
383
+
384
+ it "should return query with order by measure and other member" do
385
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
386
+ rows('[Product].children').order(['[Measures].[Unit Sales]', '[Customers].[USA]'], :asc).
387
+ to_mdx.should be_like <<-SQL
388
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
389
+ ORDER([Product].children, ([Measures].[Unit Sales], [Customers].[USA]), ASC) ON ROWS
390
+ FROM [Sales]
391
+ SQL
392
+ end
393
+
394
+ %w(top bottom).each do |extreme|
395
+ it "should return query with #{extreme} count by one measure" do
396
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
397
+ rows('[Product].children').send(:"#{extreme}_count", 5, '[Measures].[Unit Sales]').
398
+ to_mdx.should be_like <<-SQL
399
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
400
+ #{extreme.upcase}COUNT([Product].children, 5, [Measures].[Unit Sales]) ON ROWS
401
+ FROM [Sales]
402
+ SQL
403
+ end
404
+
405
+ it "should return query with #{extreme} count without measure" do
406
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
407
+ rows('[Product].children').send(:"#{extreme}_count", 5).
408
+ to_mdx.should be_like <<-SQL
409
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
410
+ #{extreme.upcase}COUNT([Product].children, 5) ON ROWS
411
+ FROM [Sales]
412
+ SQL
413
+ end
414
+
415
+ it "should return query with #{extreme} count by measure and other member" do
416
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
417
+ rows('[Product].children').send(:"#{extreme}_count", 5, ['[Measures].[Unit Sales]', '[Customers].[USA]']).
418
+ to_mdx.should be_like <<-SQL
419
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
420
+ #{extreme.upcase}COUNT([Product].children, 5, ([Measures].[Unit Sales], [Customers].[USA])) ON ROWS
421
+ FROM [Sales]
422
+ SQL
423
+ end
424
+
425
+ it "should return query with #{extreme} percent by one measure" do
426
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
427
+ rows('[Product].children').send(:"#{extreme}_percent", 20, '[Measures].[Unit Sales]').
428
+ to_mdx.should be_like <<-SQL
429
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
430
+ #{extreme.upcase}PERCENT([Product].children, 20, [Measures].[Unit Sales]) ON ROWS
431
+ FROM [Sales]
432
+ SQL
433
+ end
434
+
435
+ it "should return query with #{extreme} sum by one measure" do
436
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
437
+ rows('[Product].children').send(:"#{extreme}_sum", 1000, '[Measures].[Unit Sales]').
438
+ to_mdx.should be_like <<-SQL
439
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
440
+ #{extreme.upcase}SUM([Product].children, 1000, [Measures].[Unit Sales]) ON ROWS
441
+ FROM [Sales]
442
+ SQL
443
+ end
444
+ end
445
+
446
+ it "should return query with hierarchize" do
447
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
448
+ rows('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize.
449
+ to_mdx.should be_like <<-SQL
450
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
451
+ HIERARCHIZE({[Customers].[Country].Members, [Customers].[City].Members}) ON ROWS
452
+ FROM [Sales]
453
+ SQL
454
+ end
455
+
456
+ it "should return query with hierarchize and order" do
457
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
458
+ rows('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize(:post).
459
+ to_mdx.should be_like <<-SQL
460
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
461
+ HIERARCHIZE({[Customers].[Country].Members, [Customers].[City].Members}, POST) ON ROWS
462
+ FROM [Sales]
463
+ SQL
464
+ end
465
+
466
+ it "should return query with except" do
467
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
468
+ rows('[Customers].[Country].Members').except('[Customers].[USA]').
469
+ to_mdx.should be_like <<-SQL
470
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
471
+ EXCEPT([Customers].[Country].Members, [Customers].[USA]) ON ROWS
472
+ FROM [Sales]
473
+ SQL
474
+ end
475
+
476
+ it "should return query with filter" do
477
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
478
+ rows('[Customers].[Country].Members').filter('[Measures].[Unit Sales] > 1000').
479
+ to_mdx.should be_like <<-SQL
480
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
481
+ FILTER([Customers].[Country].Members, [Measures].[Unit Sales] > 1000) ON ROWS
482
+ FROM [Sales]
483
+ SQL
484
+ end
485
+
486
+ it "should return query with filter and set alias" do
487
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
488
+ rows('[Customers].[Country].Members').filter('NOT ISEMPTY(S.CURRENT)', :as => 'S').
489
+ to_mdx.should be_like <<-SQL
490
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
491
+ FILTER([Customers].[Country].Members AS S, NOT ISEMPTY(S.CURRENT)) ON ROWS
492
+ FROM [Sales]
493
+ SQL
494
+ end
495
+
496
+ it "should return query with filter non-empty" do
497
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
498
+ rows('[Customers].[Country].Members').filter_nonempty.
499
+ to_mdx.should be_like <<-SQL
500
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
501
+ FILTER([Customers].[Country].Members AS S, NOT ISEMPTY(S.CURRENT)) ON ROWS
502
+ FROM [Sales]
503
+ SQL
504
+ end
505
+
506
+ it "should return query including WITH MEMBER clause" do
507
+ @query.
508
+ with_member('[Measures].[ProfitPct]').
509
+ as('Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
510
+ :solve_order => 1, :format_string => 'Percent').
511
+ with_member('[Measures].[ProfitValue]').
512
+ as('[Measures].[Store Sales] * [Measures].[ProfitPct]',
513
+ :solve_order => 2, :format_string => 'Currency').
514
+ columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
515
+ rows('[Product].children').
516
+ where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
517
+ to_mdx.should be_like <<-SQL
518
+ WITH
519
+ MEMBER [Measures].[ProfitPct] AS
520
+ 'Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
521
+ SOLVE_ORDER = 1, FORMAT_STRING = 'Percent'
522
+ MEMBER [Measures].[ProfitValue] AS
523
+ '[Measures].[Store Sales] * [Measures].[ProfitPct]',
524
+ SOLVE_ORDER = 2, FORMAT_STRING = 'Currency'
525
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
526
+ [Product].children ON ROWS
527
+ FROM [Sales]
528
+ WHERE ([Time].[2010].[Q1], [Customers].[USA].[CA])
529
+ SQL
530
+ end
531
+
532
+ it "should return query including WITH SET clause" do
533
+ @query.with_set('SelectedRows').
534
+ as('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
535
+ with_member('[Measures].[Profit]').
536
+ as('[Measures].[Store Sales] - [Measures].[Store Cost]').
537
+ columns('[Measures].[Profit]').
538
+ rows('SelectedRows').
539
+ to_mdx.should be_like <<-SQL
540
+ WITH
541
+ SET SelectedRows AS
542
+ 'CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]})'
543
+ MEMBER [Measures].[Profit] AS
544
+ '[Measures].[Store Sales] - [Measures].[Store Cost]'
545
+ SELECT [Measures].[Profit] ON COLUMNS,
546
+ SelectedRows ON ROWS
547
+ FROM [Sales]
548
+ SQL
549
+ end
550
+ end
551
+
552
+ describe "execute" do
553
+ it "should return result" do
554
+ result = @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
555
+ rows('[Product].children').
556
+ where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
557
+ execute
558
+ result.values.should == sql_select_numbers(@sql_select)
559
+ end
560
+ end
561
+
562
+ describe "result HTML formatting" do
563
+ it "should format result" do
564
+ result = @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
565
+ rows('[Product].children').
566
+ where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
567
+ execute
568
+ Nokogiri::HTML.fragment(result.to_html).css('tr').size.should == (sql_select_numbers(@sql_select).size + 1)
569
+ end
570
+
571
+ # it "test" do
572
+ # puts @olap.from('Sales').
573
+ # columns('[Product].children').
574
+ # rows('[Customers].[USA].[CA].children').
575
+ # where('[Time].[2010].[Q1]', '[Measures].[Store Sales]').
576
+ # execute.to_html
577
+ # end
578
+ end
579
+
580
+ end
581
+
582
+ end