mondrian-olap 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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