mondrian-olap 0.5.0 → 1.2.0

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