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.
- checksums.yaml +4 -4
- data/Changelog.md +33 -0
- data/LICENSE-Mondrian.txt +87 -0
- data/LICENSE.txt +1 -1
- data/README.md +4 -4
- data/VERSION +1 -1
- data/lib/mondrian/jars/guava-17.0.jar +0 -0
- data/lib/mondrian/jars/log4j-api-2.17.1.jar +0 -0
- data/lib/mondrian/jars/log4j-core-2.17.1.jar +0 -0
- data/lib/mondrian/jars/log4j2-config.jar +0 -0
- data/lib/mondrian/jars/mondrian-9.3.0.0.jar +0 -0
- data/lib/mondrian/olap/connection.rb +126 -73
- data/lib/mondrian/olap/cube.rb +46 -4
- data/lib/mondrian/olap/error.rb +10 -2
- data/lib/mondrian/olap/query.rb +1 -0
- data/lib/mondrian/olap/result.rb +132 -56
- data/lib/mondrian/olap/schema.rb +9 -3
- data/lib/mondrian/olap/schema_element.rb +6 -3
- data/lib/mondrian/olap/schema_udf.rb +8 -82
- data/lib/mondrian/olap.rb +11 -7
- data/spec/connection_role_spec.rb +4 -1
- data/spec/connection_spec.rb +38 -5
- data/spec/cube_cache_control_spec.rb +7 -17
- data/spec/cube_spec.rb +36 -2
- data/spec/fixtures/MondrianTest.xml +40 -6
- data/spec/fixtures/MondrianTestOracle.xml +40 -6
- data/spec/mondrian_spec.rb +203 -1
- data/spec/query_spec.rb +94 -25
- data/spec/rake_tasks.rb +319 -165
- data/spec/schema_definition_spec.rb +8 -241
- data/spec/spec_helper.rb +330 -112
- data/spec/support/data/customers.csv +10902 -0
- data/spec/support/data/product_classes.csv +101 -0
- data/spec/support/data/products.csv +101 -0
- data/spec/support/data/promotions.csv +11 -0
- data/spec/support/data/sales.csv +101 -0
- data/spec/support/data/time.csv +731 -0
- data/spec/support/data/warehouse.csv +101 -0
- data/spec/support/matchers/be_like.rb +1 -0
- metadata +42 -83
- data/LICENSE-Mondrian.html +0 -259
- data/lib/mondrian/jars/log4j-1.2.17.jar +0 -0
- data/lib/mondrian/jars/log4j.properties +0 -3
- 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
|
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
|
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>
|
data/spec/mondrian_spec.rb
CHANGED
@@ -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
|
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, :
|
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
|
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 == [
|
919
|
-
|
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').
|