mondrian-olap 1.1.0 → 1.3.0

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