mondrian-olap 0.5.0 → 1.2.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 (54) hide show
  1. checksums.yaml +5 -5
  2. data/Changelog.md +86 -0
  3. data/LICENSE-Mondrian.txt +87 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +43 -40
  6. data/VERSION +1 -1
  7. data/lib/mondrian/jars/commons-collections-3.2.2.jar +0 -0
  8. data/lib/mondrian/jars/commons-dbcp-1.4.jar +0 -0
  9. data/lib/mondrian/jars/commons-io-2.2.jar +0 -0
  10. data/lib/mondrian/jars/commons-lang-2.6.jar +0 -0
  11. data/lib/mondrian/jars/commons-logging-1.2.jar +0 -0
  12. data/lib/mondrian/jars/commons-pool-1.5.7.jar +0 -0
  13. data/lib/mondrian/jars/commons-vfs2-2.2.jar +0 -0
  14. data/lib/mondrian/jars/eigenbase-xom-1.3.5.jar +0 -0
  15. data/lib/mondrian/jars/guava-17.0.jar +0 -0
  16. data/lib/mondrian/jars/log4j-1.2.17.jar +0 -0
  17. data/lib/mondrian/jars/log4j.properties +2 -4
  18. data/lib/mondrian/jars/mondrian-9.1.0.0.jar +0 -0
  19. data/lib/mondrian/jars/olap4j-1.2.0.jar +0 -0
  20. data/lib/mondrian/olap/connection.rb +252 -67
  21. data/lib/mondrian/olap/cube.rb +63 -2
  22. data/lib/mondrian/olap/error.rb +37 -8
  23. data/lib/mondrian/olap/query.rb +41 -21
  24. data/lib/mondrian/olap/result.rb +163 -44
  25. data/lib/mondrian/olap/schema.rb +42 -3
  26. data/lib/mondrian/olap/schema_element.rb +25 -6
  27. data/lib/mondrian/olap/schema_udf.rb +21 -16
  28. data/spec/connection_role_spec.rb +69 -13
  29. data/spec/connection_spec.rb +3 -2
  30. data/spec/cube_cache_control_spec.rb +261 -0
  31. data/spec/cube_spec.rb +32 -4
  32. data/spec/fixtures/MondrianTest.xml +1 -6
  33. data/spec/fixtures/MondrianTestOracle.xml +1 -6
  34. data/spec/mondrian_spec.rb +71 -1
  35. data/spec/query_spec.rb +323 -25
  36. data/spec/rake_tasks.rb +253 -159
  37. data/spec/schema_definition_spec.rb +314 -61
  38. data/spec/spec_helper.rb +115 -45
  39. data/spec/support/data/customers.csv +10902 -0
  40. data/spec/support/data/product_classes.csv +101 -0
  41. data/spec/support/data/products.csv +101 -0
  42. data/spec/support/data/sales.csv +101 -0
  43. data/spec/support/data/time.csv +731 -0
  44. metadata +126 -124
  45. data/LICENSE-Mondrian.html +0 -259
  46. data/lib/mondrian/jars/commons-collections-3.2.jar +0 -0
  47. data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
  48. data/lib/mondrian/jars/commons-logging-1.1.1.jar +0 -0
  49. data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
  50. data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
  51. data/lib/mondrian/jars/eigenbase-xom-1.3.1.jar +0 -0
  52. data/lib/mondrian/jars/log4j-1.2.14.jar +0 -0
  53. data/lib/mondrian/jars/mondrian.jar +0 -0
  54. data/lib/mondrian/jars/olap4j-1.0.1.539.jar +0 -0
data/spec/cube_spec.rb CHANGED
@@ -10,17 +10,21 @@ describe "Cube" do
10
10
  caption 'Sales caption'
11
11
  annotations :foo => 'bar'
12
12
  table 'sales'
13
+ visible true
13
14
  dimension 'Gender', :foreign_key => 'customer_id' do
14
15
  description 'Gender description'
15
16
  caption 'Gender caption'
17
+ visible true
16
18
  hierarchy :has_all => true, :primary_key => 'id' do
17
19
  description 'Gender hierarchy description'
18
20
  caption 'Gender hierarchy caption'
19
21
  all_member_name 'All Genders'
20
22
  all_member_caption 'All Genders caption'
21
23
  table 'customers'
24
+ visible true
22
25
  level 'Gender', :column => 'gender', :unique_members => true,
23
26
  :description => 'Gender level description', :caption => 'Gender level caption' do
27
+ visible true
24
28
  # Dimension values SQL generated by caption_expression fails on PostgreSQL and MS SQL
25
29
  if %w(mysql oracle).include?(MONDRIAN_DRIVER)
26
30
  caption_expression do
@@ -56,6 +60,10 @@ describe "Cube" do
56
60
  level 'Week', :column => 'weak_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeWeeks'
57
61
  end
58
62
  end
63
+ calculated_member 'Last week' do
64
+ hierarchy '[Time.Weekly]'
65
+ formula 'Tail([Time.Weekly].[Week].Members).Item(0)'
66
+ end
59
67
  measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum', :annotations => {:foo => 'bar'}
60
68
  measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
61
69
  measure 'Store Cost', :column => 'store_cost', :aggregator => 'sum', :visible => false
@@ -92,6 +100,10 @@ describe "Cube" do
92
100
  @olap.cube('Sales').annotations.should == {'foo' => 'bar'}
93
101
  end
94
102
 
103
+ it "should be visible" do
104
+ @olap.cube('Sales').should be_visible
105
+ end
106
+
95
107
  describe "dimensions" do
96
108
  before(:all) do
97
109
  @cube = @olap.cube('Sales')
@@ -147,6 +159,11 @@ describe "Cube" do
147
159
  it "should get dimension empty annotations" do
148
160
  @cube.dimension('Gender').annotations.should == {}
149
161
  end
162
+
163
+ it "should be visible" do
164
+ @cube.dimension('Gender').should be_visible
165
+ end
166
+
150
167
  end
151
168
 
152
169
  describe "dimension hierarchies" do
@@ -208,6 +225,11 @@ describe "Cube" do
208
225
  it "should get hierarchy empty annotations" do
209
226
  @cube.dimension('Gender').hierarchy.annotations.should == {}
210
227
  end
228
+
229
+ it "should be visible" do
230
+ @cube.dimension('Gender').hierarchies.first.should be_visible
231
+ end
232
+
211
233
  end
212
234
 
213
235
  describe "hierarchy values" do
@@ -288,6 +310,10 @@ describe "Cube" do
288
310
  @cube.dimension('Gender').hierarchy.level('Gender').annotations.should == {}
289
311
  end
290
312
 
313
+ it "should be visible" do
314
+ @cube.dimension('Gender').hierarchy.level('Gender').should be_visible
315
+ end
316
+
291
317
  end
292
318
 
293
319
  describe "members" do
@@ -356,6 +382,10 @@ describe "Cube" do
356
382
  @cube.member('[Customers].[Non-USA]').should be_calculated
357
383
  end
358
384
 
385
+ it "should be calculated when member is calculated in non-default hierarchy" do
386
+ @cube.member('[Time.Weekly].[Last week]').should be_calculated
387
+ end
388
+
359
389
  it "should not be calculated in query when calculated member defined in schema" do
360
390
  @cube.member('[Customers].[Non-USA]').should_not be_calculated_in_query
361
391
  end
@@ -384,11 +414,11 @@ describe "Cube" do
384
414
  @cube.member('[Time].[2011]').dimension_type.should == :time
385
415
  end
386
416
 
387
- it "should be visble when member is visible" do
417
+ it "should be visible when member is visible" do
388
418
  @cube.member('[Measures].[Store Sales]').should be_visible
389
419
  end
390
420
 
391
- it "should not be visble when member is not visible" do
421
+ it "should not be visible when member is not visible" do
392
422
  @cube.member('[Measures].[Store Cost]').should_not be_visible
393
423
  end
394
424
 
@@ -403,7 +433,5 @@ describe "Cube" do
403
433
  it "should get member empty annotations" do
404
434
  @cube.member('[Customers].[USA]').annotations.should == {}
405
435
  end
406
-
407
436
  end
408
-
409
437
  end
@@ -61,9 +61,6 @@ fname || ' ' || lname
61
61
  </SQL>
62
62
  <SQL dialect="mysql">
63
63
  CONCAT(`customers`.`fname`, ' ', `customers`.`lname`)
64
- </SQL>
65
- <SQL dialect="luciddb">
66
- "fname" || ' ' || "lname"
67
64
  </SQL>
68
65
  <SQL dialect="generic">
69
66
  fullname
@@ -78,15 +75,13 @@ fname || ' ' || lname
78
75
  </SQL>
79
76
  <SQL dialect="mysql">
80
77
  CONCAT(`customers`.`fname`, ' ', `customers`.`lname`)
81
- </SQL>
82
- <SQL dialect="luciddb">
83
- "fname" || ' ' || "lname"
84
78
  </SQL>
85
79
  <SQL dialect="generic">
86
80
  fullname
87
81
  </SQL>
88
82
  </OrdinalExpression>
89
83
  <Property name="Gender" column="gender"/>
84
+ <Property name="Description" column="description"/>
90
85
  </Level>
91
86
  </Hierarchy>
92
87
  </Dimension>
@@ -61,9 +61,6 @@ fname || ' ' || lname
61
61
  </SQL>
62
62
  <SQL dialect="mysql">
63
63
  CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)
64
- </SQL>
65
- <SQL dialect="luciddb">
66
- fname || ' ' || lname
67
64
  </SQL>
68
65
  <SQL dialect="generic">
69
66
  FULLNAME
@@ -78,15 +75,13 @@ fname || ' ' || lname
78
75
  </SQL>
79
76
  <SQL dialect="mysql">
80
77
  CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)
81
- </SQL>
82
- <SQL dialect="luciddb">
83
- fname || ' ' || lname
84
78
  </SQL>
85
79
  <SQL dialect="generic">
86
80
  FULLNAME
87
81
  </SQL>
88
82
  </OrdinalExpression>
89
83
  <Property name="Gender" column="GENDER"/>
84
+ <Property name="Description" column="DESCRIPTION"/>
90
85
  </Level>
91
86
  </Hierarchy>
92
87
  </Dimension>
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require "spec_helper"
2
4
 
3
5
  describe "Mondrian features" do
@@ -11,13 +13,39 @@ describe "Mondrian features" do
11
13
  level 'Gender', :column => 'gender', :unique_members => true
12
14
  end
13
15
  end
16
+ dimension 'Promotions', :foreign_key => 'promotion_id' do
17
+ hierarchy :has_all => true, :primary_key => 'id' do
18
+ table 'promotions'
19
+ level 'Promotion', :column => 'id', :name_column => 'promotion', :unique_members => true, :ordinal_column => 'sequence', :type => 'Numeric'
20
+ end
21
+ end
22
+ dimension 'Linked Promotions', :foreign_key => 'customer_id' do
23
+ hierarchy :has_all => true, :primary_key => 'id', :primary_key_table => 'customers' do
24
+ join :left_key => 'related_fullname', :right_key => 'fullname' do
25
+ table "customers"
26
+ join :left_key => "promotion_id", :right_key => "id" do
27
+ table "customers", :alias => "customers_bt"
28
+ table "promotions"
29
+ end
30
+ end
31
+ level 'Promotion', :column => 'id', :name_column => 'promotion', :unique_members => true, :table => 'promotions', :ordinal_column => 'sequence', :type => 'Numeric', :approx_row_count => 10
32
+ end
33
+ end
14
34
  dimension 'Customers', :foreign_key => 'customer_id' do
15
35
  hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
16
36
  table 'customers'
17
37
  level 'Country', :column => 'country', :unique_members => true
18
38
  level 'State Province', :column => 'state_province', :unique_members => true
19
39
  level 'City', :column => 'city', :unique_members => false
20
- level 'Name', :column => 'fullname', :unique_members => true
40
+ level 'Name', :column => 'fullname', :unique_members => true do
41
+ property 'Related name', :column => 'related_fullname', :type => "String"
42
+ end
43
+ end
44
+ hierarchy 'ID', :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
45
+ table 'customers'
46
+ level 'ID', :column => 'id', :type => 'Numeric', :internal_type => 'long', :unique_members => true do
47
+ property 'Name', :column => 'fullname'
48
+ end
21
49
  end
22
50
  end
23
51
  dimension 'Time', :foreign_key => 'time_id', :type => 'TimeDimension' do
@@ -50,4 +78,46 @@ describe "Mondrian features" do
50
78
  end.should_not raise_error
51
79
  end
52
80
 
81
+ # test for https://jira.pentaho.com/browse/MONDRIAN-2683
82
+ it "should order crossjoin of rows" do
83
+ lambda do
84
+ @olap.from('Sales').
85
+ columns('[Measures].[Unit Sales]').
86
+ rows('[Customers].[Country].Members').crossjoin('[Gender].[Gender].Members').
87
+ order('[Measures].[Unit Sales]', :bdesc).
88
+ execute
89
+ end.should_not raise_error
90
+ end
91
+
92
+ it "should generate correct member name from large number key" do
93
+ result = @olap.from('Sales').
94
+ columns("Filter([Customers.ID].[ID].Members, [Customers.ID].CurrentMember.Properties('Name') = 'Big Number')").
95
+ execute
96
+ result.column_names.should == ["10000000000"]
97
+ end
98
+
99
+ # test for https://jira.pentaho.com/browse/MONDRIAN-990
100
+ it "should return result when diacritical marks used" do
101
+ full_name = '[Customers].[USA].[CA].[Rīga]'
102
+ result = @olap.from('Sales').columns(full_name).execute
103
+ result.column_full_names.should == [full_name]
104
+ end
105
+
106
+ it "should execute MDX with join tables" do
107
+ # Load dimension members in Mondrian cache as the problem occurred when searching members in the cache
108
+ @olap.from('Sales').columns('CROSSJOIN({[Linked Promotions].[Promotion].[Promotion 2]}, [Customers].[Name].Members)').execute
109
+
110
+ mdx = <<~MDX
111
+ SELECT
112
+ NON EMPTY FILTER(
113
+ CROSSJOIN({[Linked Promotions].[Promotion].[Promotion 2]}, [Customers].[Name].Members),
114
+ (([Measures].[Unit Sales]) <> 0)
115
+ ) ON ROWS,
116
+ [Measures].[Unit Sales] ON COLUMNS
117
+ FROM [Sales]
118
+ MDX
119
+
120
+ expect { @olap.execute mdx }.not_to raise_error
121
+ end
122
+
53
123
  end
data/spec/query_spec.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe "Query" do
4
- def quote_table_name(name)
5
- ActiveRecord::Base.connection.quote_table_name(name)
4
+ def qt(name)
5
+ ActiveRecord::Base.connection.quote_table_name(name.to_s)
6
6
  end
7
7
 
8
8
  before(:all) do
@@ -21,9 +21,9 @@ describe "Query" do
21
21
  FROM sales
22
22
  LEFT JOIN products ON sales.product_id = products.id
23
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
24
+ LEFT JOIN #{qt :time} ON sales.time_id = #{qt :time}.id
25
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'
26
+ WHERE #{qt :time}.the_year = 2010 AND #{qt :time}.quarter = 'Q1'
27
27
  AND customers.country = 'USA' AND customers.state_province = 'CA'
28
28
  GROUP BY product_classes.product_family
29
29
  ORDER BY product_classes.product_family
@@ -179,6 +179,13 @@ describe "Query" do
179
179
  end
180
180
  end
181
181
 
182
+ describe "distinct" do
183
+ it "should limit to set of distinct tuples" do
184
+ @query.rows('[Product].children').distinct.nonempty.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]')
185
+ @query.rows.should == [:nonempty, [:distinct, ["[Product].children"]]]
186
+ end
187
+ end
188
+
182
189
  describe "order" do
183
190
  it "should order by one measure" do
184
191
  @query.rows('[Product].children').order('[Measures].[Unit Sales]', :bdesc)
@@ -282,6 +289,18 @@ describe "Query" do
282
289
  end
283
290
  end
284
291
 
292
+ describe "generate" do
293
+ it "should generate new set" do
294
+ @query.rows('[Customers].[Country].Members').generate('[Customers].CurrentMember')
295
+ @query.rows.should == [:generate, ['[Customers].[Country].Members'], ['[Customers].CurrentMember']]
296
+ end
297
+
298
+ it "should generate new set with all option" do
299
+ @query.rows('[Customers].[Country].Members').generate('[Customers].CurrentMember', :all)
300
+ @query.rows.should == [:generate, ['[Customers].[Country].Members'], ['[Customers].CurrentMember'], 'ALL']
301
+ end
302
+ end
303
+
285
304
  describe "where" do
286
305
  it "should accept conditions" do
287
306
  @query.where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').should equal(@query)
@@ -605,6 +624,26 @@ describe "Query" do
605
624
  SQL
606
625
  end
607
626
 
627
+ it "should return query with generate" do
628
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
629
+ rows('[Customers].[Country].Members').generate('[Customers].CurrentMember').
630
+ to_mdx.should be_like <<-SQL
631
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
632
+ GENERATE([Customers].[Country].Members, [Customers].CurrentMember) ON ROWS
633
+ FROM [Sales]
634
+ SQL
635
+ end
636
+
637
+ it "should return query with generate all" do
638
+ @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
639
+ rows('[Customers].[Country].Members').generate('[Customers].CurrentMember', :all).
640
+ to_mdx.should be_like <<-SQL
641
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
642
+ GENERATE([Customers].[Country].Members, [Customers].CurrentMember, ALL) ON ROWS
643
+ FROM [Sales]
644
+ SQL
645
+ end
646
+
608
647
  it "should return query including WITH MEMBER clause" do
609
648
  @query.
610
649
  with_member('[Measures].[ProfitPct]').
@@ -632,20 +671,25 @@ describe "Query" do
632
671
  end
633
672
 
634
673
  it "should return query including WITH SET clause" do
635
- @query.with_set('SelectedRows').
674
+ @query.with_set('CrossJoinSet').
636
675
  as('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
676
+ with_set('MemberSet').as('[Product].[All Products]').
677
+ with_set('FunctionSet').as('[Product].AllMembers').
678
+ with_set('ItemSet').as('[Product].AllMembers.Item(0)').
637
679
  with_member('[Measures].[Profit]').
638
680
  as('[Measures].[Store Sales] - [Measures].[Store Cost]').
639
681
  columns('[Measures].[Profit]').
640
- rows('SelectedRows').
682
+ rows('CrossJoinSet').
641
683
  to_mdx.should be_like <<-SQL
642
684
  WITH
643
- SET SelectedRows AS
644
- 'CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]})'
685
+ SET CrossJoinSet AS 'CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]})'
686
+ SET MemberSet AS '{[Product].[All Products]}'
687
+ SET FunctionSet AS '[Product].AllMembers'
688
+ SET ItemSet AS '{[Product].AllMembers.Item(0)}'
645
689
  MEMBER [Measures].[Profit] AS
646
690
  '[Measures].[Store Sales] - [Measures].[Store Cost]'
647
691
  SELECT {[Measures].[Profit]} ON COLUMNS,
648
- SelectedRows ON ROWS
692
+ CrossJoinSet ON ROWS
649
693
  FROM [Sales]
650
694
  SQL
651
695
  end
@@ -717,6 +761,17 @@ describe "Query" do
717
761
  }
718
762
  end
719
763
 
764
+ it "should raise error when TokenMgrError is raised" do
765
+ expect {
766
+ @query.with_member('[Measures].[Dummy]').as('[Measures].[Store Sales]]').
767
+ columns('[Measures].[Dummy]').execute
768
+ }.to raise_error {|e|
769
+ e.should be_kind_of(Mondrian::OLAP::Error)
770
+ e.message.should =~ /mondrian\.parser\.TokenMgrError/
771
+ e.root_cause_message.should =~ /Lexical error/
772
+ }
773
+ end
774
+
720
775
  end
721
776
 
722
777
  describe "drill through cell" do
@@ -733,7 +788,7 @@ describe "Query" do
733
788
  @drill_through.column_types.should == [
734
789
  :INT, :VARCHAR, :INT, :INT, :INT,
735
790
  :VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR,
736
- :VARCHAR, :VARCHAR, :VARCHAR, :INT,
791
+ :VARCHAR, :VARCHAR, :VARCHAR, :BIGINT,
737
792
  :VARCHAR,
738
793
  :DECIMAL
739
794
  ]
@@ -752,12 +807,14 @@ describe "Query" do
752
807
  end if %w(mysql postgresql).include? MONDRIAN_DRIVER
753
808
 
754
809
  it "should return table names" do
755
- @drill_through.table_names.should == [
810
+ # ignore calculated customer full name column name which is shown differently on each database
811
+ @drill_through.table_names[0..12].should == [
756
812
  "time", "time", "time", "time", "time",
757
813
  "product_classes", "product_classes", "product_classes", "product_classes", "products", "products",
758
- "customers", "customers", "", "customers",
759
- "customers",
760
- "sales"
814
+ "customers", "customers"
815
+ ]
816
+ @drill_through.table_names[14..16].should == [
817
+ "customers", "customers", "sales"
761
818
  ]
762
819
  end if %w(mysql postgresql).include? MONDRIAN_DRIVER
763
820
 
@@ -776,8 +833,7 @@ describe "Query" do
776
833
  end
777
834
 
778
835
  it "should return correct row value types" do
779
- @drill_through.rows.first.map(&:class).should ==
780
- case MONDRIAN_DRIVER
836
+ expected_value_types = case MONDRIAN_DRIVER
781
837
  when "oracle"
782
838
  [
783
839
  BigDecimal, String, BigDecimal, BigDecimal, BigDecimal,
@@ -788,21 +844,26 @@ describe "Query" do
788
844
  ]
789
845
  when 'mssql'
790
846
  [
791
- Fixnum, String, Fixnum, Fixnum, Fixnum,
847
+ Integer, String, Integer, Integer, Integer,
792
848
  String, String, String, String, String, String,
793
- String, String, String, BigDecimal,
849
+ # last one can be BigDecimal or Integer, probably depends on MS SQL version
850
+ String, String, String, Numeric,
794
851
  String,
795
852
  BigDecimal
796
853
  ]
797
854
  else
798
855
  [
799
- Fixnum, String, Fixnum, Fixnum, Fixnum,
856
+ Integer, String, Integer, Integer, Integer,
800
857
  String, String, String, String, String, String,
801
- String, String, String, Fixnum,
858
+ String, String, String, Integer,
802
859
  String,
803
860
  BigDecimal
804
861
  ]
805
862
  end
863
+
864
+ @drill_through.rows.first.each_with_index do |value, i|
865
+ value.should be_a expected_value_types[i]
866
+ end
806
867
  end
807
868
 
808
869
  it "should return only specified max rows" do
@@ -816,8 +877,7 @@ describe "Query" do
816
877
  @query = @olap.from('Sales')
817
878
  @result = @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
818
879
  rows('[Product].children').
819
- # where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
820
- where('[Time].[2010].[Q1]').
880
+ where('[Time].[2010].[Q1]', '[Time].[2010].[Q2]').
821
881
  execute
822
882
  end
823
883
 
@@ -829,9 +889,9 @@ describe "Query" do
829
889
  '[Measures].[Unit Sales]', '[Measures].[Store Sales]'
830
890
  ])
831
891
  @drill_through.column_labels.should == [
832
- "Month",
833
- "City",
834
- "Product Family",
892
+ "Month (Key)",
893
+ "City (Key)",
894
+ "Product Family (Key)",
835
895
  "Unit Sales", "Store Sales"
836
896
  ]
837
897
  end
@@ -847,6 +907,78 @@ describe "Query" do
847
907
  @drill_through.rows.all?{|r| r.any?{|c| c}}.should be_true
848
908
  end
849
909
 
910
+ it "should return member name and property values" do
911
+ @drill_through = @result.drill_through(row: 0, column: 0,
912
+ return: [
913
+ "Name([Customers].[Name])",
914
+ "Property([Customers].[Name], 'Gender')",
915
+ "Property([Customers].[Name], 'Description')",
916
+ "Property([Customers].[Name], 'Very long non-existing property name')"
917
+ ]
918
+ )
919
+ @drill_through.column_labels.should == [
920
+ "Name", "Gender", "Description",
921
+ "Very long non-existing property name"[0, MONDRIAN_DRIVER == 'oracle' ? 30 : 9999]
922
+ ]
923
+ @drill_through.rows.should == @sql.select_rows(<<-SQL)
924
+ SELECT
925
+ customers.fullname,
926
+ customers.gender,
927
+ customers.description,
928
+ '' as non_existing
929
+ FROM
930
+ sales,
931
+ customers,
932
+ time,
933
+ products,
934
+ product_classes
935
+ WHERE
936
+ (time.quarter = 'Q1' OR time.quarter = 'Q2') AND
937
+ time.the_year = 2010 AND
938
+ product_classes.product_family = 'Drink' AND
939
+ products.product_class_id = product_classes.id AND
940
+ sales.product_id = products.id AND
941
+ sales.time_id = time.id AND
942
+ customers.id = sales.customer_id
943
+ ORDER BY
944
+ customers.fullname,
945
+ customers.gender,
946
+ customers.description
947
+ SQL
948
+ end
949
+
950
+ it "should group by" do
951
+ @drill_through = @result.drill_through(row: 0, column: 0,
952
+ return: [
953
+ "[Product].[Product Family]",
954
+ "[Measures].[Unit Sales]",
955
+ "[Measures].[Store Cost]"
956
+ ],
957
+ group_by: true
958
+ )
959
+ @drill_through.column_labels.should == [ "Product Family (Key)", "Unit Sales", "Store Cost" ]
960
+ @drill_through.rows.should == @sql.select_rows(<<-SQL
961
+ SELECT
962
+ product_classes.product_family,
963
+ SUM(sales.unit_sales) AS unit_sales,
964
+ SUM(sales.store_cost) AS store_cost
965
+ FROM
966
+ sales,
967
+ time,
968
+ products,
969
+ product_classes
970
+ WHERE
971
+ (time.quarter = 'Q1' OR time.quarter = 'Q2') AND
972
+ time.the_year = 2010 AND
973
+ product_classes.product_family = 'Drink' AND
974
+ products.product_class_id = product_classes.id AND
975
+ sales.product_id = products.id AND
976
+ sales.time_id = time.id
977
+ GROUP BY
978
+ product_classes.product_family
979
+ SQL
980
+ )
981
+ end
850
982
  end
851
983
 
852
984
  describe "drill through statement" do
@@ -895,4 +1027,170 @@ describe "Query" do
895
1027
 
896
1028
  end
897
1029
 
1030
+ describe "schema cache" do
1031
+ before(:all) do
1032
+ product_id = @sql.select_value("SELECT MIN(id) FROM products")
1033
+ time_id = @sql.select_value("SELECT MIN(id) FROM #{qt :time}")
1034
+ customer_id = @sql.select_value("SELECT MIN(id) FROM customers")
1035
+ @condition = "product_id = #{product_id} AND time_id = #{time_id} AND customer_id = #{customer_id}"
1036
+ # check expected initial value
1037
+ @first_unit_sales = 1
1038
+ @sql.select_value("SELECT unit_sales FROM sales WHERE #{@condition}").to_i.should == @first_unit_sales
1039
+ end
1040
+
1041
+ before do
1042
+ create_olap_connection
1043
+ @unit_sales = query_unit_sales_value
1044
+
1045
+ update_first_unit_sales(@first_unit_sales + 1)
1046
+
1047
+ # should still use previous value from cache
1048
+ create_olap_connection
1049
+ query_unit_sales_value.should == @unit_sales
1050
+ end
1051
+
1052
+ after do
1053
+ update_first_unit_sales(@first_unit_sales)
1054
+ Mondrian::OLAP::Connection.flush_schema_cache
1055
+ end
1056
+
1057
+ def create_olap_connection(options = {})
1058
+ @olap2.close if @olap2
1059
+ @olap2 = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG.merge(options))
1060
+ end
1061
+
1062
+ def update_first_unit_sales(value)
1063
+ @sql.update "UPDATE sales SET unit_sales = #{value} WHERE #{@condition}"
1064
+ end
1065
+
1066
+ def query_unit_sales_value
1067
+ @olap2.from('Sales').columns('[Measures].[Unit Sales]').execute.values.first
1068
+ end
1069
+
1070
+ it "should flush schema cache" do
1071
+ @olap2.flush_schema
1072
+ create_olap_connection
1073
+ query_unit_sales_value.should == @unit_sales + 1
1074
+ end
1075
+
1076
+ it "should remove schema by key" do
1077
+ Mondrian::OLAP::Connection.flush_schema(@olap2.schema_key)
1078
+ create_olap_connection
1079
+ query_unit_sales_value.should == @unit_sales + 1
1080
+ end
1081
+
1082
+ end
1083
+
1084
+ describe "profiling" do
1085
+ before(:all) do
1086
+ if @olap
1087
+ @olap.flush_schema
1088
+ @olap.close
1089
+ end
1090
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
1091
+ @result = @olap.execute "SELECT [Measures].[Unit Sales] ON COLUMNS, [Product].Children ON ROWS FROM [Sales]", profiling: true
1092
+ @result.profiling_mark_full("MDX query time", 100)
1093
+ end
1094
+
1095
+ it "should return query plan" do
1096
+ @result.profiling_plan.strip.should == <<-EOS.strip
1097
+ Axis (COLUMNS):
1098
+ SetListCalc(name=SetListCalc, class=class mondrian.olap.fun.SetFunDef$SetListCalc, type=SetType<MemberType<member=[Measures].[Unit Sales]>>, resultStyle=MUTABLE_LIST)
1099
+ 2(name=2, class=class mondrian.olap.fun.SetFunDef$SetListCalc$2, type=MemberType<member=[Measures].[Unit Sales]>, resultStyle=VALUE)
1100
+ Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType<member=[Measures].[Unit Sales]>, resultStyle=VALUE_NOT_NULL, value=[Measures].[Unit Sales])
1101
+
1102
+ Axis (ROWS):
1103
+ Children(name=Children, class=class mondrian.olap.fun.BuiltinFunTable$22$1, type=SetType<MemberType<hierarchy=[Product]>>, resultStyle=LIST)
1104
+ CurrentMemberFixed(hierarchy=[Product], name=CurrentMemberFixed, class=class mondrian.olap.fun.HierarchyCurrentMemberFunDef$FixedCalcImpl, type=MemberType<hierarchy=[Product]>, resultStyle=VALUE)
1105
+ EOS
1106
+ end
1107
+
1108
+ it "should return SQL timing string" do
1109
+ @result.profiling_timing_string.strip.should =~
1110
+ %r{^SqlStatement-Segment.load invoked 1 times for total of \d+ms. \(Avg. \d+ms/invocation\)$}
1111
+ end
1112
+
1113
+ it "should return custom profiling string" do
1114
+ @result.profiling_timing_string.strip.should =~
1115
+ %r{^MDX query time invoked 1 times for total of 100ms. \(Avg. 100ms/invocation\)$}
1116
+ end
1117
+
1118
+ it "should return total duration" do
1119
+ @result.total_duration.should > 0
1120
+ end
1121
+ end
1122
+
1123
+ describe "error with profiling" do
1124
+ before(:all) do
1125
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
1126
+ begin
1127
+ @olap.execute <<-MDX, profiling: true
1128
+ SELECT [Measures].[Unit Sales] ON COLUMNS,
1129
+ FILTER([Customers].Children, ([Customers].DefaultMember, [Measures].[Unit Sales]) > 'dummy') ON ROWS
1130
+ FROM [Sales]
1131
+ MDX
1132
+ rescue => e
1133
+ @error = e
1134
+ end
1135
+ end
1136
+
1137
+ it "should return query plan" do
1138
+ @error.profiling_plan.should =~ /^Axis \(COLUMNS\):/
1139
+ end
1140
+
1141
+ it "should return timing string" do
1142
+ @error.profiling_timing_string.should =~
1143
+ %r{^FilterFunDef invoked 1 times for total of \d+ms. \(Avg. \d+ms/invocation\)$}
1144
+ end
1145
+ end
1146
+
1147
+ describe "timeout" do
1148
+ before(:all) do
1149
+ @schema = Mondrian::OLAP::Schema.new
1150
+ @schema.define do
1151
+ cube 'Sales' do
1152
+ table 'sales'
1153
+ dimension 'Customers', foreign_key: 'customer_id' do
1154
+ hierarchy all_member_name: 'All Customers', primary_key: 'id' do
1155
+ table 'customers'
1156
+ level 'Name', column: 'fullname'
1157
+ end
1158
+ end
1159
+ calculated_member 'Sleep 5' do
1160
+ dimension 'Measures'
1161
+ formula 'Sleep(5)'
1162
+ end
1163
+ calculated_member 'Sleep 0' do
1164
+ dimension 'Measures'
1165
+ formula 'Sleep(0)'
1166
+ end
1167
+ end
1168
+ user_defined_function 'Sleep' do
1169
+ ruby do
1170
+ parameters :numeric
1171
+ returns :numeric
1172
+ def call(n)
1173
+ sleep n
1174
+ n
1175
+ end
1176
+ end
1177
+ end
1178
+ end
1179
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge schema: @schema)
1180
+ end
1181
+
1182
+ it "should raise timeout error for long queries" do
1183
+ expect do
1184
+ @olap.from('Sales').columns('[Measures].[Sleep 5]').execute(timeout: 0.1)
1185
+ end.to raise_error do |e|
1186
+ e.should be_kind_of(Mondrian::OLAP::Error)
1187
+ e.message.should == 'org.olap4j.OlapException: Mondrian Error:Query timeout of 0 seconds reached'
1188
+ end
1189
+ end
1190
+
1191
+ it "should not raise timeout error for short queries" do
1192
+ @olap.from('Sales').columns('[Measures].[Sleep 0]').execute(timeout: 1).values.should == [0]
1193
+ end
1194
+ end
1195
+
898
1196
  end