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.
- 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').
|