mondrian-olap 1.1.0 → 1.3.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 (44) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +33 -0
  3. data/LICENSE-Mondrian.txt +87 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +4 -4
  6. data/VERSION +1 -1
  7. data/lib/mondrian/jars/guava-17.0.jar +0 -0
  8. data/lib/mondrian/jars/log4j-api-2.17.1.jar +0 -0
  9. data/lib/mondrian/jars/log4j-core-2.17.1.jar +0 -0
  10. data/lib/mondrian/jars/log4j2-config.jar +0 -0
  11. data/lib/mondrian/jars/mondrian-9.3.0.0.jar +0 -0
  12. data/lib/mondrian/olap/connection.rb +126 -73
  13. data/lib/mondrian/olap/cube.rb +46 -4
  14. data/lib/mondrian/olap/error.rb +10 -2
  15. data/lib/mondrian/olap/query.rb +1 -0
  16. data/lib/mondrian/olap/result.rb +132 -56
  17. data/lib/mondrian/olap/schema.rb +9 -3
  18. data/lib/mondrian/olap/schema_element.rb +6 -3
  19. data/lib/mondrian/olap/schema_udf.rb +8 -82
  20. data/lib/mondrian/olap.rb +11 -7
  21. data/spec/connection_role_spec.rb +4 -1
  22. data/spec/connection_spec.rb +38 -5
  23. data/spec/cube_cache_control_spec.rb +7 -17
  24. data/spec/cube_spec.rb +36 -2
  25. data/spec/fixtures/MondrianTest.xml +40 -6
  26. data/spec/fixtures/MondrianTestOracle.xml +40 -6
  27. data/spec/mondrian_spec.rb +203 -1
  28. data/spec/query_spec.rb +94 -25
  29. data/spec/rake_tasks.rb +319 -165
  30. data/spec/schema_definition_spec.rb +8 -241
  31. data/spec/spec_helper.rb +330 -112
  32. data/spec/support/data/customers.csv +10902 -0
  33. data/spec/support/data/product_classes.csv +101 -0
  34. data/spec/support/data/products.csv +101 -0
  35. data/spec/support/data/promotions.csv +11 -0
  36. data/spec/support/data/sales.csv +101 -0
  37. data/spec/support/data/time.csv +731 -0
  38. data/spec/support/data/warehouse.csv +101 -0
  39. data/spec/support/matchers/be_like.rb +1 -0
  40. metadata +42 -83
  41. data/LICENSE-Mondrian.html +0 -259
  42. data/lib/mondrian/jars/log4j-1.2.17.jar +0 -0
  43. data/lib/mondrian/jars/log4j.properties +0 -3
  44. data/lib/mondrian/jars/mondrian-8.3.0.5.jar +0 -0
data/spec/cube_spec.rb CHANGED
@@ -166,6 +166,31 @@ describe "Cube" do
166
166
 
167
167
  end
168
168
 
169
+ describe "cube hierarchies" do
170
+ before(:all) do
171
+ @cube = @olap.cube('Sales')
172
+ @hierarchy_names = %w(Measures Gender Customers Time Time.Weekly)
173
+ end
174
+
175
+ it "should get hierarchy names" do
176
+ @cube.hierarchy_names.should == @hierarchy_names
177
+ end
178
+
179
+ it "should get hierarchy by name" do
180
+ @cube.hierarchy('Gender').name.should == 'Gender'
181
+ end
182
+
183
+ it "should get hierarchy dimension name" do
184
+ hierarchy = @cube.hierarchy('Time.Weekly')
185
+ hierarchy.dimension.name.should == 'Time'
186
+ hierarchy.dimension_name.should == 'Time'
187
+ end
188
+
189
+ it "should return nil when getting dimension with invalid name" do
190
+ @cube.hierarchy('invalid').should be_nil
191
+ end
192
+ end
193
+
169
194
  describe "dimension hierarchies" do
170
195
  before(:all) do
171
196
  @cube = @olap.cube('Sales')
@@ -218,6 +243,15 @@ describe "Cube" do
218
243
  @cube.dimension('Gender').hierarchy.levels.map(&:members_count).should == [1, 2]
219
244
  end
220
245
 
246
+ it "should set and get hierarchy level cardinality" do
247
+ level = @cube.dimension('Gender').hierarchy.levels.last
248
+ level.cardinality.should == Java::JavaLang::Integer::MIN_VALUE
249
+ level.cardinality = 2
250
+ @olap.cube('Sales').dimension('Gender').hierarchy.levels.last.cardinality.should == 2
251
+ level.cardinality = nil
252
+ @olap.cube('Sales').dimension('Gender').hierarchy.levels.last.cardinality.should == Java::JavaLang::Integer::MIN_VALUE
253
+ end
254
+
221
255
  it "should get hierarchy annotations" do
222
256
  @cube.dimension('Customers').hierarchy.annotations.should == {'foo' => 'bar'}
223
257
  end
@@ -238,12 +272,12 @@ describe "Cube" do
238
272
  end
239
273
 
240
274
  it "should get hierarchy all member" do
241
- @cube.dimension('Gender').hierarchy.has_all?.should be_true
275
+ @cube.dimension('Gender').hierarchy.has_all?.should == true
242
276
  @cube.dimension('Gender').hierarchy.all_member_name.should == 'All Genders'
243
277
  end
244
278
 
245
279
  it "should not get all member for hierarchy without all member" do
246
- @cube.dimension('Time').hierarchy.has_all?.should be_false
280
+ @cube.dimension('Time').hierarchy.has_all?.should == false
247
281
  @cube.dimension('Time').hierarchy.all_member_name.should be_nil
248
282
  end
249
283
 
@@ -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,9 +75,6 @@ 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
@@ -132,4 +126,44 @@ fullname
132
126
  </CalculatedMember>
133
127
  </Cube>
134
128
 
129
+ <Cube name="Warehouse">
130
+ <Table name="warehouse"/>
131
+ <DimensionUsage name="Time" source="Time" foreignKey="time_id"/>
132
+ <DimensionUsage name="Product" source="Product" foreignKey="product_id"/>
133
+ <Measure aggregator="sum" column="units_shipped" formatString="#,##0" name="Units Shipped"/>
134
+ <Measure aggregator="sum" column="store_invoice" formatString="#,##0.00" name="Store Invoice"/>
135
+ <Measure name="Products with units shipped" aggregator="distinct-count" formatString="#,###">
136
+ <MeasureExpression>
137
+ <SQL dialect="generic">
138
+ CASE WHEN units_shipped IS NOT NULL THEN product_id END
139
+ </SQL>
140
+ </MeasureExpression>
141
+ </Measure>
142
+ </Cube>
143
+
144
+ <VirtualCube name="Sales and Warehouse">
145
+ <VirtualCubeDimension name="Customers" cubeName="Sales"/>
146
+ <VirtualCubeDimension name="Gender" cubeName="Sales"/>
147
+ <VirtualCubeDimension name="Product"/>
148
+ <VirtualCubeDimension name="Time"/>
149
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Unit Sales]"/>
150
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Store Cost]"/>
151
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Store Sales]"/>
152
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Sales Count]"/>
153
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Customer Count]"/>
154
+ <VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Units Shipped]"/>
155
+ <VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Store Invoice]"/>
156
+ <VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Products with units shipped]"/>
157
+ </VirtualCube>
158
+
159
+ <Role name="Canada manager">
160
+ <SchemaGrant access="none">
161
+ <CubeGrant access="all" cube="Sales">
162
+ <HierarchyGrant access="custom" hierarchy="[Customers]">
163
+ <MemberGrant access="all" member="[Customers].[Canada]"/>
164
+ </HierarchyGrant>
165
+ </CubeGrant>
166
+ </SchemaGrant>
167
+ </Role>
168
+
135
169
  </Schema>
@@ -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,9 +75,6 @@ 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
@@ -132,4 +126,44 @@ FULLNAME
132
126
  </CalculatedMember>
133
127
  </Cube>
134
128
 
129
+ <Cube name="Warehouse">
130
+ <Table name="WAREHOUSE"/>
131
+ <DimensionUsage name="Time" source="Time" foreignKey="TIME_ID"/>
132
+ <DimensionUsage name="Product" source="Product" foreignKey="PRODUCT_ID"/>
133
+ <Measure aggregator="sum" column="UNITS_SHIPPED" formatString="#,##0" name="Units Shipped"/>
134
+ <Measure aggregator="sum" column="STORE_INVOICE" formatString="#,##0.00" name="Store Invoice"/>
135
+ <Measure name="Products with units shipped" aggregator="distinct-count" formatString="#,###">
136
+ <MeasureExpression>
137
+ <SQL dialect="generic">
138
+ CASE WHEN UNITS_SHIPPED IS NOT NULL THEN PRODUCT_ID END
139
+ </SQL>
140
+ </MeasureExpression>
141
+ </Measure>
142
+ </Cube>
143
+
144
+ <VirtualCube name="Sales and Warehouse">
145
+ <VirtualCubeDimension name="Customers" cubeName="Sales"/>
146
+ <VirtualCubeDimension name="Gender" cubeName="Sales"/>
147
+ <VirtualCubeDimension name="Product"/>
148
+ <VirtualCubeDimension name="Time"/>
149
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Unit Sales]"/>
150
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Store Cost]"/>
151
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Store Sales]"/>
152
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Sales Count]"/>
153
+ <VirtualCubeMeasure cubeName="Sales" name="[Measures].[Customer Count]"/>
154
+ <VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Units Shipped]"/>
155
+ <VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Store Invoice]"/>
156
+ <VirtualCubeMeasure cubeName="Warehouse" name="[Measures].[Products with units shipped]"/>
157
+ </VirtualCube>
158
+
159
+ <Role name="Canada manager">
160
+ <SchemaGrant access="none">
161
+ <CubeGrant access="all" cube="Sales">
162
+ <HierarchyGrant access="custom" hierarchy="[Customers]">
163
+ <MemberGrant access="all" member="[Customers].[Canada]"/>
164
+ </HierarchyGrant>
165
+ </CubeGrant>
166
+ </SchemaGrant>
167
+ </Role>
168
+
135
169
  </Schema>
@@ -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,40 @@ 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
+ property 'Birthdate', :column => 'birthdate', :type => "String"
43
+ end
44
+ end
45
+ hierarchy 'ID', :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
46
+ table 'customers'
47
+ level 'ID', :column => 'id', :type => 'Numeric', :internal_type => 'long', :unique_members => true do
48
+ property 'Name', :column => 'fullname'
49
+ end
21
50
  end
22
51
  end
23
52
  dimension 'Time', :foreign_key => 'time_id', :type => 'TimeDimension' do
@@ -26,6 +55,9 @@ describe "Mondrian features" do
26
55
  level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
27
56
  level 'Quarter', :column => 'quarter', :unique_members => false, :level_type => 'TimeQuarters'
28
57
  level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeMonths'
58
+ level 'Day', :column => 'day_of_month', :type => 'Numeric', :unique_members => false, :level_type => 'TimeDays' do
59
+ property 'Date', :column => 'the_date', :type => "String"
60
+ end
29
61
  end
30
62
  hierarchy 'Weekly', :has_all => false, :primary_key => 'id' do
31
63
  table 'time'
@@ -36,6 +68,17 @@ describe "Mondrian features" do
36
68
  measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum'
37
69
  measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
38
70
  end
71
+
72
+ user_defined_function 'IsDirty' do
73
+ ruby do
74
+ returns :scalar
75
+ syntax :function
76
+ def call_with_evaluator(evaluator)
77
+ evaluator.isDirty
78
+ end
79
+ end
80
+ end
81
+
39
82
  end
40
83
  @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
41
84
  end
@@ -50,4 +93,163 @@ describe "Mondrian features" do
50
93
  end.should_not raise_error
51
94
  end
52
95
 
96
+ # test for https://jira.pentaho.com/browse/MONDRIAN-2683
97
+ it "should order crossjoin of rows" do
98
+ lambda do
99
+ @olap.from('Sales').
100
+ columns('[Measures].[Unit Sales]').
101
+ rows('[Customers].[Country].Members').crossjoin('[Gender].[Gender].Members').
102
+ order('[Measures].[Unit Sales]', :bdesc).
103
+ execute
104
+ end.should_not raise_error
105
+ end
106
+
107
+ it "should generate correct member name from large number key" do
108
+ result = @olap.from('Sales').
109
+ columns("Filter([Customers.ID].[ID].Members, [Customers.ID].CurrentMember.Properties('Name') = 'Big Number')").
110
+ execute
111
+ result.column_names.should == ["10000000000"]
112
+ end
113
+
114
+ # test for https://jira.pentaho.com/browse/MONDRIAN-990
115
+ it "should return result when diacritical marks used" do
116
+ full_name = '[Customers].[USA].[CA].[Rīga]'
117
+ result = @olap.from('Sales').columns(full_name).execute
118
+ result.column_full_names.should == [full_name]
119
+ end
120
+
121
+ it "should execute MDX with join tables" do
122
+ # Load dimension members in Mondrian cache as the problem occurred when searching members in the cache
123
+ @olap.from('Sales').columns('CROSSJOIN({[Linked Promotions].[Promotion].[Promotion 2]}, [Customers].[Name].Members)').execute
124
+
125
+ mdx = <<~MDX
126
+ SELECT
127
+ NON EMPTY FILTER(
128
+ CROSSJOIN({[Linked Promotions].[Promotion].[Promotion 2]}, [Customers].[Name].Members),
129
+ (([Measures].[Unit Sales]) <> 0)
130
+ ) ON ROWS,
131
+ [Measures].[Unit Sales] ON COLUMNS
132
+ FROM [Sales]
133
+ MDX
134
+
135
+ expect { @olap.execute mdx }.not_to raise_error
136
+ end
137
+
138
+ # Test for https://jira.pentaho.com/browse/MONDRIAN-2714
139
+ it "should return datetime property as java.sql.Timestamp" do
140
+ full_name = '[2010].[Q1].[1].[1]'
141
+ member = @olap.cube('Sales').member(full_name)
142
+ member.property_value('Date').should be_a(java.sql.Timestamp)
143
+
144
+ result = @olap.from('Sales').
145
+ with_member('[Measures].[date]').as("#{full_name}.Properties('Date')", format_string: 'dd.mm.yyyy').
146
+ columns('[Measures].[date]').execute
147
+ result.values.first.should be_a(java.sql.Timestamp)
148
+ result.formatted_values.first.should == '01.01.2010'
149
+ end
150
+
151
+ it "should return date property as java.sql.Date" do
152
+ expected_date_class =
153
+ case MONDRIAN_DRIVER
154
+ when 'oracle'
155
+ java.sql.Timestamp
156
+ else
157
+ java.sql.Date
158
+ end
159
+
160
+ member = @olap.cube('Sales').hierarchy('Customers').level('Name').members.first
161
+ date_value = member.property_value('Birthdate')
162
+ date_value.should be_a(expected_date_class)
163
+
164
+ result = @olap.from('Sales').
165
+ with_member('[Measures].[date]').as("#{member.full_name}.Properties('Birthdate')", format_string: 'dd.mm.yyyy').
166
+ columns('[Measures].[date]').execute
167
+ result.values.first.should be_a(expected_date_class)
168
+ result.formatted_values.first.should == Date.parse(date_value.to_s).strftime("%d.%m.%Y")
169
+ end
170
+
171
+ describe "optimized Aggregate" do
172
+ def expected_value(crossjoin_members = nil)
173
+ query = @olap.from('Sales').columns('[Measures].[Unit Sales]')
174
+ query = query.crossjoin(crossjoin_members) if crossjoin_members
175
+ query.rows('[Customers].[USA].[CA]', '[Customers].[USA].[OR]').
176
+ execute.values.map(&:first).inject(&:+)
177
+ end
178
+
179
+ it "should aggregate stored members" do
180
+ result = @olap.from('Sales').
181
+ with_member('[Customers].[CA and OR]').as("Aggregate({[Customers].[USA].[CA], [Customers].[USA].[OR]})").
182
+ columns('[Measures].[Unit Sales]').
183
+ rows('[Customers].[CA and OR]').execute
184
+ result.values[0][0].should == expected_value
185
+ end
186
+
187
+ it "should aggregate stored members from several dimensions" do
188
+ result = @olap.from('Sales').
189
+ with_member('[Customers].[CA and OR]').
190
+ as("Aggregate({[Gender].[F]} * {[Customers].[USA].[CA], [Customers].[USA].[OR]})").
191
+ columns('[Measures].[Unit Sales]').
192
+ rows('[Customers].[CA and OR]').execute
193
+ result.values[0][0].should == expected_value('[Gender].[F]')
194
+ end
195
+
196
+ it "should aggregate stored members and a measure" do
197
+ result = @olap.from('Sales').
198
+ with_member('[Measures].[CA and OR]').
199
+ as("Aggregate({[Customers].[USA].[CA], [Customers].[USA].[OR]} * {[Measures].[Unit Sales]})").
200
+ columns('[Measures].[CA and OR]').execute
201
+ result.values[0].should == expected_value
202
+ end
203
+
204
+ it "should aggregate stored members with expression" do
205
+ result = @olap.from('Sales').
206
+ with_member('[Measures].[CA and OR twice]').
207
+ as("Aggregate({[Customers].[USA].[CA], [Customers].[USA].[OR]}, [Measures].[Unit Sales] * 2)").
208
+ columns('[Measures].[CA and OR twice]').execute
209
+ result.values[0].should == expected_value * 2
210
+ end
211
+
212
+ it "should aggregate calculated aggregate members" do
213
+ result = @olap.from('Sales').
214
+ with_member('[Customers].[CA calculated]').as("Aggregate({[Customers].[USA].[CA]})").
215
+ with_member('[Customers].[OR calculated]').as("Aggregate({[Customers].[USA].[OR]})").
216
+ with_member('[Customers].[CA and OR]').as("Aggregate({[Customers].[CA calculated], [Customers].[OR calculated]})").
217
+ columns('[Measures].[Unit Sales]').
218
+ rows('[Customers].[CA and OR]').execute
219
+ result.values[0][0].should == expected_value
220
+ end
221
+ end
222
+
223
+ it "should call evaluator isDirty method" do
224
+ result = @olap.from('Sales').
225
+ with_member('[Measures].[is dirty]').as('IsDirty()').
226
+ columns('[Measures].[is dirty]').execute
227
+ result.values[0].should == false
228
+ end
229
+
230
+ it "should support multiple values IN expression" do
231
+ lambda do
232
+ @olap.from('Sales').
233
+ columns('[Measures].[Unit Sales]').
234
+ where('[Time].[2011].[Q1]', '[Time].[2011].[Q2]').
235
+ execute
236
+ end.should_not raise_error
237
+ end
238
+
239
+ describe "functions with double argument" do
240
+ it "should get Abs with decimal measure" do
241
+ result = @olap.from('Sales').
242
+ with_member('[Measures].[Abs Store Sales]').as('Abs([Measures].[Store Sales])').
243
+ columns('[Measures].[Store Sales]', '[Measures].[Abs Store Sales]').execute
244
+ result.values[0].should == result.values[1]
245
+ end
246
+
247
+ it "should get Round with decimal measure" do
248
+ result = @olap.from('Sales').
249
+ with_member('[Measures].[Round Store Sales]').as('Round([Measures].[Store Sales])').
250
+ columns('[Measures].[Store Sales]', '[Measures].[Round Store Sales]').
251
+ where('[Customers].[USA].[CA]').execute
252
+ result.values[0].round.should == result.values[1]
253
+ end
254
+ end
53
255
  end
data/spec/query_spec.rb CHANGED
@@ -101,7 +101,7 @@ describe "Query" do
101
101
  end
102
102
 
103
103
  it "should return formatted cells" do
104
- @result.formatted_values.map{|r| r.map{|s| BigDecimal.new(s.gsub(',',''))}}.should == @expected_result_values
104
+ @result.formatted_values.map{|r| r.map{|s| BigDecimal(s.gsub(',',''))}}.should == @expected_result_values
105
105
  end
106
106
 
107
107
  end
@@ -703,6 +703,11 @@ describe "Query" do
703
703
  execute
704
704
  result.values.should == sql_select_numbers(@sql_select)
705
705
  end
706
+
707
+ it "should not fail without columns" do
708
+ result = @query.rows('[Product].DefaultMember').execute
709
+ result.values.should == [[]]
710
+ end
706
711
  end
707
712
 
708
713
  describe "result HTML formatting" do
@@ -713,14 +718,6 @@ describe "Query" do
713
718
  execute
714
719
  Nokogiri::HTML.fragment(result.to_html).css('tr').size.should == (sql_select_numbers(@sql_select).size + 1)
715
720
  end
716
-
717
- # it "test" do
718
- # puts @olap.from('Sales').
719
- # columns('[Product].children').
720
- # rows('[Customers].[USA].[CA].children').
721
- # where('[Time].[2010].[Q1]', '[Measures].[Store Sales]').
722
- # execute.to_html
723
- # end
724
721
  end
725
722
 
726
723
  end
@@ -788,7 +785,7 @@ describe "Query" do
788
785
  @drill_through.column_types.should == [
789
786
  :INT, :VARCHAR, :INT, :INT, :INT,
790
787
  :VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR,
791
- :VARCHAR, :VARCHAR, :VARCHAR, :INT,
788
+ :VARCHAR, :VARCHAR, :VARCHAR, :BIGINT,
792
789
  :VARCHAR,
793
790
  :DECIMAL
794
791
  ]
@@ -842,15 +839,6 @@ describe "Query" do
842
839
  String,
843
840
  BigDecimal
844
841
  ]
845
- when 'mssql'
846
- [
847
- Integer, String, Integer, Integer, Integer,
848
- String, String, String, String, String, String,
849
- # last one can be BigDecimal or Integer, probably depends on MS SQL version
850
- String, String, String, Numeric,
851
- String,
852
- BigDecimal
853
- ]
854
842
  else
855
843
  [
856
844
  Integer, String, Integer, Integer, Integer,
@@ -896,6 +884,25 @@ describe "Query" do
896
884
  ]
897
885
  end
898
886
 
887
+ it "should return rows also for field dimension that is not present in the report query" do
888
+ result = @olap.from('Sales').columns('[Measures].[Unit Sales]').rows('[Customers].[Canada].[BC].[Burnaby]').execute
889
+ drill_through = result.drill_through(row: 0, column: 0, return: ["[Product].[Product Family]"])
890
+ drill_through.rows.should == @sql.select_rows(<<-SQL)
891
+ SELECT
892
+ product_classes.product_family
893
+ FROM
894
+ sales,
895
+ products,
896
+ product_classes,
897
+ customers
898
+ WHERE
899
+ products.product_class_id = product_classes.id AND
900
+ sales.product_id = products.id AND
901
+ sales.customer_id = customers.id AND
902
+ customers.country = 'Canada' AND customers.state_province = 'BC' AND customers.city = 'Burnaby'
903
+ SQL
904
+ end
905
+
899
906
  it "should return only nonempty measures" do
900
907
  @drill_through = @result.drill_through(:row => 0, :column => 0,
901
908
  :return => "[Measures].[Unit Sales], [Measures].[Store Sales]",
@@ -904,7 +911,7 @@ describe "Query" do
904
911
  @drill_through.column_labels.should == [
905
912
  "Unit Sales", "Store Sales"
906
913
  ]
907
- @drill_through.rows.all?{|r| r.any?{|c| c}}.should be_true
914
+ @drill_through.rows.all?{|r| r.any?{|c| c}}.should == true
908
915
  end
909
916
 
910
917
  it "should return member name and property values" do
@@ -912,15 +919,20 @@ describe "Query" do
912
919
  return: [
913
920
  "Name([Customers].[Name])",
914
921
  "Property([Customers].[Name], 'Gender')",
915
- "Property([Customers].[Name], 'Description')"
922
+ "Property([Customers].[Name], 'Description')",
923
+ "Property([Customers].[Name], 'Non-existing property name')"
916
924
  ]
917
925
  )
918
- @drill_through.column_labels.should == [ "Name", "Gender", "Description" ]
919
- @drill_through.rows.should == @sql.select_rows(<<-SQL
926
+ @drill_through.column_labels.should == [
927
+ "Name", "Gender", "Description",
928
+ "Non-existing property name"
929
+ ]
930
+ @drill_through.rows.should == @sql.select_rows(<<-SQL)
920
931
  SELECT
921
932
  customers.fullname,
922
933
  customers.gender,
923
- customers.description
934
+ customers.description,
935
+ '' as non_existing
924
936
  FROM
925
937
  sales,
926
938
  customers,
@@ -940,7 +952,6 @@ describe "Query" do
940
952
  customers.gender,
941
953
  customers.description
942
954
  SQL
943
- )
944
955
  end
945
956
 
946
957
  it "should group by" do
@@ -977,6 +988,64 @@ describe "Query" do
977
988
  end
978
989
  end
979
990
 
991
+ describe "drill through cell with return and role restrictions" do
992
+ before(:all) do
993
+ @olap.role_name = "Canada manager"
994
+ @query = @olap.from('Sales')
995
+ @result = @query.columns('[Measures].[Unit Sales]').
996
+ rows('[Customers].[All Customers]').
997
+ execute
998
+ end
999
+
1000
+ after(:all) do
1001
+ @olap.role_name = nil
1002
+ end
1003
+
1004
+ it "should return data according to role restriction" do
1005
+ @drill_through = @result.drill_through(:row => 0, :column => 0, :return => [
1006
+ '[Customers].[Country]',
1007
+ '[Measures].[Unit Sales]'
1008
+ ])
1009
+ @drill_through.rows.all? { |r| r.first == "Canada" }.should == true
1010
+ end
1011
+ end
1012
+
1013
+ describe "drill through virtual cube cell with return" do
1014
+ before(:all) do
1015
+ @query = @olap.from('Sales and Warehouse')
1016
+ @result = @query.columns(
1017
+ '[Measures].[Unit Sales]', '[Measures].[Store Sales]',
1018
+ '[Measures].[Units Shipped]', '[Measures].[Products with units shipped]'
1019
+ ).
1020
+ rows('[Product].children').
1021
+ where('[Time].[2010].[Q1]', '[Time].[2010].[Q2]').
1022
+ execute
1023
+ end
1024
+
1025
+ it "should return specified fields from other cubes as empty strings" do
1026
+ @drill_through = @result.drill_through(:row => 0, :column => 3, :return => [
1027
+ '[Time].[Month]',
1028
+ '[Product].[Product Family]',
1029
+ '[Customers].[City]', # missing in Warehouse cube
1030
+ '[Measures].[Unit Sales]', # missing in Warehouse cube
1031
+ '[Measures].[Units Shipped]',
1032
+ '[Measures].[Products with units shipped]'
1033
+ ])
1034
+ @drill_through.column_labels.should == [
1035
+ "Month (Key)",
1036
+ "Product Family (Key)",
1037
+ "City (Key)",
1038
+ "Unit Sales",
1039
+ "Units Shipped",
1040
+ "Products with units shipped"
1041
+ ]
1042
+ # Validate that only City and Unit Sales values are missing
1043
+ @drill_through.rows.map { |r| r.map(&:present?) }.uniq.should == [
1044
+ [true, true, false, false, true, true]
1045
+ ]
1046
+ end
1047
+ end
1048
+
980
1049
  describe "drill through statement" do
981
1050
  before(:all) do
982
1051
  @query = @olap.from('Sales').