mondrian-olap 0.4.0-java
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.
- data/.rspec +2 -0
- data/Changelog.md +60 -0
- data/Gemfile +21 -0
- data/LICENSE-Mondrian.html +259 -0
- data/LICENSE.txt +22 -0
- data/README.md +302 -0
- data/RUNNING_TESTS.rdoc +66 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/lib/mondrian-olap.rb +1 -0
- data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
- data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
- data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
- data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
- data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
- data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
- data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
- data/lib/mondrian/jars/javacup.jar +0 -0
- data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
- data/lib/mondrian/jars/log4j.properties +5 -0
- data/lib/mondrian/jars/mondrian.jar +0 -0
- data/lib/mondrian/jars/olap4j.jar +0 -0
- data/lib/mondrian/olap.rb +17 -0
- data/lib/mondrian/olap/connection.rb +201 -0
- data/lib/mondrian/olap/cube.rb +297 -0
- data/lib/mondrian/olap/error.rb +57 -0
- data/lib/mondrian/olap/query.rb +342 -0
- data/lib/mondrian/olap/result.rb +264 -0
- data/lib/mondrian/olap/schema.rb +378 -0
- data/lib/mondrian/olap/schema_element.rb +153 -0
- data/lib/mondrian/olap/schema_udf.rb +282 -0
- data/mondrian-olap.gemspec +128 -0
- data/spec/connection_role_spec.rb +130 -0
- data/spec/connection_spec.rb +72 -0
- data/spec/cube_spec.rb +318 -0
- data/spec/fixtures/MondrianTest.xml +134 -0
- data/spec/fixtures/MondrianTestOracle.xml +134 -0
- data/spec/mondrian_spec.rb +53 -0
- data/spec/query_spec.rb +807 -0
- data/spec/rake_tasks.rb +260 -0
- data/spec/schema_definition_spec.rb +1249 -0
- data/spec/spec_helper.rb +134 -0
- data/spec/support/matchers/be_like.rb +24 -0
- metadata +278 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
<?xml version="1.0"?>
|
2
|
+
<Schema name="MondrianTest">
|
3
|
+
<Dimension name="Time" type="TimeDimension">
|
4
|
+
<Hierarchy hasAll="false" primaryKey="ID">
|
5
|
+
<Table name="TIME"/>
|
6
|
+
<Level name="Year" column="THE_YEAR" type="Numeric" uniqueMembers="true"
|
7
|
+
levelType="TimeYears"/>
|
8
|
+
<Level name="Quarter" column="QUARTER" uniqueMembers="false"
|
9
|
+
levelType="TimeQuarters"/>
|
10
|
+
<Level name="Month" column="MONTH_OF_YEAR" uniqueMembers="false" type="Numeric"
|
11
|
+
levelType="TimeMonths"/>
|
12
|
+
</Hierarchy>
|
13
|
+
<Hierarchy hasAll="true" name="Weekly" primaryKey="ID">
|
14
|
+
<Table name="TIME"/>
|
15
|
+
<Level name="Year" column="THE_YEAR" type="Numeric" uniqueMembers="true"
|
16
|
+
levelType="TimeYears"/>
|
17
|
+
<Level name="Week" column="WEEK_OF_YEAR" type="Numeric" uniqueMembers="false"
|
18
|
+
levelType="TimeWeeks"/>
|
19
|
+
<Level name="Day" column="DAY_OF_MONTH" uniqueMembers="false" type="Numeric"
|
20
|
+
levelType="TimeDays"/>
|
21
|
+
</Hierarchy>
|
22
|
+
</Dimension>
|
23
|
+
|
24
|
+
<Dimension name="Product">
|
25
|
+
<Hierarchy hasAll="true" primaryKey="ID" primaryKeyTable="PRODUCTS">
|
26
|
+
<Join leftKey="PRODUCT_CLASS_ID" rightKey="ID">
|
27
|
+
<Table name="PRODUCTS"/>
|
28
|
+
<Table name="PRODUCT_CLASSES"/>
|
29
|
+
</Join>
|
30
|
+
<Level name="Product Family" table="PRODUCT_CLASSES" column="PRODUCT_FAMILY"
|
31
|
+
uniqueMembers="true"/>
|
32
|
+
<Level name="Product Department" table="PRODUCT_CLASSES" column="PRODUCT_DEPARTMENT"
|
33
|
+
uniqueMembers="false"/>
|
34
|
+
<Level name="Product Category" table="PRODUCT_CLASSES" column="PRODUCT_CATEGORY"
|
35
|
+
uniqueMembers="false"/>
|
36
|
+
<Level name="Product Subcategory" table="PRODUCT_CLASSES" column="PRODUCT_SUBCATEGORY"
|
37
|
+
uniqueMembers="false"/>
|
38
|
+
<Level name="Brand Name" table="PRODUCTS" column="BRAND_NAME" uniqueMembers="false"/>
|
39
|
+
<Level name="Product Name" table="PRODUCTS" column="PRODUCT_NAME"
|
40
|
+
uniqueMembers="true"/>
|
41
|
+
</Hierarchy>
|
42
|
+
</Dimension>
|
43
|
+
|
44
|
+
<Cube name="Sales" defaultMeasure="Unit Sales">
|
45
|
+
<Table name="SALES"/>
|
46
|
+
<DimensionUsage name="Time" source="Time" foreignKey="TIME_ID"/>
|
47
|
+
<DimensionUsage name="Product" source="Product" foreignKey="PRODUCT_ID"/>
|
48
|
+
<Dimension name="Customers" foreignKey="CUSTOMER_ID">
|
49
|
+
<Hierarchy hasAll="true" allMemberName="All Customers" primaryKey="ID">
|
50
|
+
<Table name="CUSTOMERS"/>
|
51
|
+
<Level name="Country" column="COUNTRY" uniqueMembers="true"/>
|
52
|
+
<Level name="State Province" column="STATE_PROVINCE" uniqueMembers="true"/>
|
53
|
+
<Level name="City" column="CITY" uniqueMembers="false"/>
|
54
|
+
<Level name="Name" column="ID" type="Numeric" uniqueMembers="true">
|
55
|
+
<NameExpression>
|
56
|
+
<SQL dialect="oracle">
|
57
|
+
fname || ' ' || lname
|
58
|
+
</SQL>
|
59
|
+
<SQL dialect="postgres">
|
60
|
+
"fname" || ' ' || "lname"
|
61
|
+
</SQL>
|
62
|
+
<SQL dialect="mysql">
|
63
|
+
CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)
|
64
|
+
</SQL>
|
65
|
+
<SQL dialect="luciddb">
|
66
|
+
fname || ' ' || lname
|
67
|
+
</SQL>
|
68
|
+
<SQL dialect="generic">
|
69
|
+
FULLNAME
|
70
|
+
</SQL>
|
71
|
+
</NameExpression>
|
72
|
+
<OrdinalExpression>
|
73
|
+
<SQL dialect="oracle">
|
74
|
+
fname || ' ' || lname
|
75
|
+
</SQL>
|
76
|
+
<SQL dialect="postgres">
|
77
|
+
"fname" || ' ' || "lname"
|
78
|
+
</SQL>
|
79
|
+
<SQL dialect="mysql">
|
80
|
+
CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)
|
81
|
+
</SQL>
|
82
|
+
<SQL dialect="luciddb">
|
83
|
+
fname || ' ' || lname
|
84
|
+
</SQL>
|
85
|
+
<SQL dialect="generic">
|
86
|
+
FULLNAME
|
87
|
+
</SQL>
|
88
|
+
</OrdinalExpression>
|
89
|
+
<Property name="Gender" column="GENDER"/>
|
90
|
+
</Level>
|
91
|
+
</Hierarchy>
|
92
|
+
</Dimension>
|
93
|
+
<Dimension name="Gender" foreignKey="CUSTOMER_ID">
|
94
|
+
<Hierarchy hasAll="true" allMemberName="All Gender" primaryKey="ID">
|
95
|
+
<Table name="CUSTOMERS"/>
|
96
|
+
<Level name="Gender" column="GENDER" uniqueMembers="true"/>
|
97
|
+
</Hierarchy>
|
98
|
+
</Dimension>
|
99
|
+
|
100
|
+
<Measure name="Unit Sales" column="UNIT_SALES" aggregator="sum"
|
101
|
+
formatString="Standard"/>
|
102
|
+
<Measure name="Store Cost" column="STORE_COST" aggregator="sum"
|
103
|
+
formatString="#,###.00"/>
|
104
|
+
<Measure name="Store Sales" column="STORE_SALES" aggregator="sum"
|
105
|
+
formatString="#,###.00"/>
|
106
|
+
<Measure name="Sales Count" column="PRODUCT_ID" aggregator="count"
|
107
|
+
formatString="#,###"/>
|
108
|
+
<Measure name="Customer Count" column="CUSTOMER_ID"
|
109
|
+
aggregator="distinct-count" formatString="#,###"/>
|
110
|
+
<CalculatedMember
|
111
|
+
name="Profit"
|
112
|
+
dimension="Measures">
|
113
|
+
<Formula>[Measures].[Store Sales] - [Measures].[Store Cost]</Formula>
|
114
|
+
<CalculatedMemberProperty name="FORMAT_STRING" value="$#,##0.00"/>
|
115
|
+
</CalculatedMember>
|
116
|
+
<CalculatedMember
|
117
|
+
name="Profit last Period"
|
118
|
+
dimension="Measures"
|
119
|
+
formula="COALESCEEMPTY((Measures.[Profit], [Time].[Time].PREVMEMBER), Measures.[Profit])"
|
120
|
+
visible="false">
|
121
|
+
<CalculatedMemberProperty name="FORMAT_STRING" value="$#,##0.00"/>
|
122
|
+
<CalculatedMemberProperty name="MEMBER_ORDINAL" value="18"/>
|
123
|
+
</CalculatedMember>
|
124
|
+
<CalculatedMember
|
125
|
+
name="Profit Growth"
|
126
|
+
dimension="Measures"
|
127
|
+
formula="([Measures].[Profit] - [Measures].[Profit last Period]) / [Measures].[Profit last Period]"
|
128
|
+
visible="true"
|
129
|
+
caption="Gewinn-Wachstum">
|
130
|
+
<CalculatedMemberProperty name="FORMAT_STRING" value="0.0%"/>
|
131
|
+
</CalculatedMember>
|
132
|
+
</Cube>
|
133
|
+
|
134
|
+
</Schema>
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Mondrian features" do
|
4
|
+
before(:all) do
|
5
|
+
@schema = Mondrian::OLAP::Schema.define do
|
6
|
+
cube 'Sales' do
|
7
|
+
table 'sales'
|
8
|
+
dimension 'Gender', :foreign_key => 'customer_id' do
|
9
|
+
hierarchy :has_all => true, :primary_key => 'id' do
|
10
|
+
table 'customers'
|
11
|
+
level 'Gender', :column => 'gender', :unique_members => true
|
12
|
+
end
|
13
|
+
end
|
14
|
+
dimension 'Customers', :foreign_key => 'customer_id' do
|
15
|
+
hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
|
16
|
+
table 'customers'
|
17
|
+
level 'Country', :column => 'country', :unique_members => true
|
18
|
+
level 'State Province', :column => 'state_province', :unique_members => true
|
19
|
+
level 'City', :column => 'city', :unique_members => false
|
20
|
+
level 'Name', :column => 'fullname', :unique_members => true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
dimension 'Time', :foreign_key => 'time_id', :type => 'TimeDimension' do
|
24
|
+
hierarchy :has_all => false, :primary_key => 'id' do
|
25
|
+
table 'time'
|
26
|
+
level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
|
27
|
+
level 'Quarter', :column => 'quarter', :unique_members => false, :level_type => 'TimeQuarters'
|
28
|
+
level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeMonths'
|
29
|
+
end
|
30
|
+
hierarchy 'Weekly', :has_all => false, :primary_key => 'id' do
|
31
|
+
table 'time'
|
32
|
+
level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
|
33
|
+
level 'Week', :column => 'weak_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeWeeks'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum'
|
37
|
+
measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
|
41
|
+
end
|
42
|
+
|
43
|
+
# test for http://jira.pentaho.com/browse/MONDRIAN-1050
|
44
|
+
it "should order rows by DateTime expression" do
|
45
|
+
lambda do
|
46
|
+
@olap.from('Sales').
|
47
|
+
columns('[Measures].[Unit Sales]').
|
48
|
+
rows('[Customers].children').order('Now()', :asc).
|
49
|
+
execute
|
50
|
+
end.should_not raise_error
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/spec/query_spec.rb
ADDED
@@ -0,0 +1,807 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Query" do
|
4
|
+
def quote_table_name(name)
|
5
|
+
ActiveRecord::Base.connection.quote_table_name(name)
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:all) do
|
9
|
+
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
|
10
|
+
@sql = ActiveRecord::Base.connection
|
11
|
+
|
12
|
+
@query_string = <<-SQL
|
13
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
14
|
+
{[Product].children} ON ROWS
|
15
|
+
FROM [Sales]
|
16
|
+
WHERE ([Time].[2010].[Q1], [Customers].[USA].[CA])
|
17
|
+
SQL
|
18
|
+
|
19
|
+
@sql_select = <<-SQL
|
20
|
+
SELECT SUM(unit_sales) unit_sales_sum, SUM(store_sales) store_sales_sum
|
21
|
+
FROM sales
|
22
|
+
LEFT JOIN products ON sales.product_id = products.id
|
23
|
+
LEFT JOIN product_classes ON products.product_class_id = product_classes.id
|
24
|
+
LEFT JOIN #{quote_table_name('time')} ON sales.time_id = #{quote_table_name('time')}.id
|
25
|
+
LEFT JOIN customers ON sales.customer_id = customers.id
|
26
|
+
WHERE #{quote_table_name('time')}.the_year = 2010 AND #{quote_table_name('time')}.quarter = 'Q1'
|
27
|
+
AND customers.country = 'USA' AND customers.state_province = 'CA'
|
28
|
+
GROUP BY product_classes.product_family
|
29
|
+
ORDER BY product_classes.product_family
|
30
|
+
SQL
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
def sql_select_numbers(select_string)
|
35
|
+
@sql.select_rows(select_string).map do |rows|
|
36
|
+
rows.map{|col| BigDecimal(col.to_s)}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "result" do
|
41
|
+
before(:all) do
|
42
|
+
|
43
|
+
# TODO: replace hardcoded expected values with result of SQL query
|
44
|
+
@expected_column_names = ["Unit Sales", "Store Sales"]
|
45
|
+
@expected_column_full_names = ["[Measures].[Unit Sales]", "[Measures].[Store Sales]"]
|
46
|
+
@expected_drillable_columns = [false, false]
|
47
|
+
@expected_row_names = ["Drink", "Food", "Non-Consumable"]
|
48
|
+
@expected_row_full_names = ["[Product].[Drink]", "[Product].[Food]", "[Product].[Non-Consumable]"]
|
49
|
+
@expected_drillable_rows = [true, true, true]
|
50
|
+
|
51
|
+
# AR JDBC driver always returns strings, need to convert to BigDecimal
|
52
|
+
@expected_result_values = sql_select_numbers(@sql_select)
|
53
|
+
|
54
|
+
@expected_result_values_by_columns =
|
55
|
+
[@expected_result_values.map{|row| row[0]}, @expected_result_values.map{|row| row[1]}]
|
56
|
+
|
57
|
+
@result = @olap.execute @query_string
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should return axes" do
|
61
|
+
@result.axes_count.should == 2
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should return column names" do
|
65
|
+
@result.column_names.should == @expected_column_names
|
66
|
+
@result.column_full_names.should == @expected_column_full_names
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should return row names" do
|
70
|
+
@result.row_names.should == @expected_row_names
|
71
|
+
@result.row_full_names.should == @expected_row_full_names
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should return axis by index names" do
|
75
|
+
@result.axis_names[0].should == @expected_column_names
|
76
|
+
@result.axis_full_names[0].should == @expected_column_full_names
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should return column members" do
|
80
|
+
@result.column_members.map(&:name).should == @expected_column_names
|
81
|
+
@result.column_members.map(&:full_name).should == @expected_column_full_names
|
82
|
+
@result.column_members.map(&:"drillable?").should == @expected_drillable_columns
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should return row members" do
|
86
|
+
@result.row_members.map(&:name).should == @expected_row_names
|
87
|
+
@result.row_members.map(&:full_name).should == @expected_row_full_names
|
88
|
+
@result.row_members.map(&:"drillable?").should == @expected_drillable_rows
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should return cells" do
|
92
|
+
@result.values.should == @expected_result_values
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should return cells with specified axes number sequence" do
|
96
|
+
@result.values(0, 1).should == @expected_result_values_by_columns
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should return cells with specified axes name sequence" do
|
100
|
+
@result.values(:columns, :rows).should == @expected_result_values_by_columns
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should return formatted cells" do
|
104
|
+
@result.formatted_values.map{|r| r.map{|s| BigDecimal.new(s.gsub(',',''))}}.should == @expected_result_values
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
describe "builder" do
|
110
|
+
|
111
|
+
before(:each) do
|
112
|
+
@query = @olap.from('Sales')
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "from cube" do
|
116
|
+
it "should return query" do
|
117
|
+
@query.should be_a(Mondrian::OLAP::Query)
|
118
|
+
@query.cube_name.should == 'Sales'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "columns" do
|
123
|
+
it "should accept list" do
|
124
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').should equal(@query)
|
125
|
+
@query.columns.should == ['[Measures].[Unit Sales]', '[Measures].[Store Sales]']
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should accept list as array" do
|
129
|
+
@query.columns(['[Measures].[Unit Sales]', '[Measures].[Store Sales]'])
|
130
|
+
@query.columns.should == ['[Measures].[Unit Sales]', '[Measures].[Store Sales]']
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should accept with several method calls" do
|
134
|
+
@query.columns('[Measures].[Unit Sales]').columns('[Measures].[Store Sales]')
|
135
|
+
@query.columns.should == ['[Measures].[Unit Sales]', '[Measures].[Store Sales]']
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "other axis" do
|
140
|
+
it "should accept axis with index member list" do
|
141
|
+
@query.axis(0, '[Measures].[Unit Sales]', '[Measures].[Store Sales]')
|
142
|
+
@query.axis(0).should == ['[Measures].[Unit Sales]', '[Measures].[Store Sales]']
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should accept rows list" do
|
146
|
+
@query.rows('[Product].children')
|
147
|
+
@query.rows.should == ['[Product].children']
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should accept pages list" do
|
151
|
+
@query.pages('[Product].children')
|
152
|
+
@query.pages.should == ['[Product].children']
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe "crossjoin" do
|
157
|
+
it "should do crossjoin of several dimensions" do
|
158
|
+
@query.rows('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]')
|
159
|
+
@query.rows.should == [:crossjoin, ['[Product].children'], ['[Customers].[Canada]', '[Customers].[USA]']]
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should do crossjoin passing array as first argument" do
|
163
|
+
@query.rows('[Product].children').crossjoin(['[Customers].[Canada]', '[Customers].[USA]'])
|
164
|
+
@query.rows.should == [:crossjoin, ['[Product].children'], ['[Customers].[Canada]', '[Customers].[USA]']]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "nonempty_crossjoin" do
|
169
|
+
it "should do nonempty_crossjoin of several dimensions" do
|
170
|
+
@query.rows('[Product].children').nonempty_crossjoin('[Customers].[Canada]', '[Customers].[USA]')
|
171
|
+
@query.rows.should == [:nonempty_crossjoin, ['[Product].children'], ['[Customers].[Canada]', '[Customers].[USA]']]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "nonempty" do
|
176
|
+
it "should limit to set of members with nonempty values" do
|
177
|
+
@query.rows('[Product].children').nonempty
|
178
|
+
@query.rows.should == [:nonempty, ['[Product].children']]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe "order" do
|
183
|
+
it "should order by one measure" do
|
184
|
+
@query.rows('[Product].children').order('[Measures].[Unit Sales]', :bdesc)
|
185
|
+
@query.rows.should == [:order, ['[Product].children'], '[Measures].[Unit Sales]', 'BDESC']
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should order using String order direction" do
|
189
|
+
@query.rows('[Product].children').order('[Measures].[Unit Sales]', 'DESC')
|
190
|
+
@query.rows.should == [:order, ['[Product].children'], '[Measures].[Unit Sales]', 'DESC']
|
191
|
+
end
|
192
|
+
|
193
|
+
it "should order by measure and other member" do
|
194
|
+
@query.rows('[Product].children').order(['[Measures].[Unit Sales]', '[Customers].[USA]'], :basc)
|
195
|
+
@query.rows.should == [:order, ['[Product].children'], ['[Measures].[Unit Sales]', '[Customers].[USA]'], 'BASC']
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
%w(top bottom).each do |extreme|
|
200
|
+
describe extreme do
|
201
|
+
it "should select #{extreme} count rows by measure" do
|
202
|
+
@query.rows('[Product].children').send(:"#{extreme}_count", 5, '[Measures].[Unit Sales]')
|
203
|
+
@query.rows.should == [:"#{extreme}_count", ['[Product].children'], 5, '[Measures].[Unit Sales]']
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should select #{extreme} count rows without measure" do
|
207
|
+
@query.rows('[Product].children').send(:"#{extreme}_count", 5)
|
208
|
+
@query.rows.should == [:"#{extreme}_count", ['[Product].children'], 5]
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should select #{extreme} percent rows by measure" do
|
212
|
+
@query.rows('[Product].children').send(:"#{extreme}_percent", 20, '[Measures].[Unit Sales]')
|
213
|
+
@query.rows.should == [:"#{extreme}_percent", ['[Product].children'], 20, '[Measures].[Unit Sales]']
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should select #{extreme} sum rows by measure" do
|
217
|
+
@query.rows('[Product].children').send(:"#{extreme}_sum", 1000, '[Measures].[Unit Sales]')
|
218
|
+
@query.rows.should == [:"#{extreme}_sum", ['[Product].children'], 1000, '[Measures].[Unit Sales]']
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "hierarchize" do
|
224
|
+
it "should hierarchize simple set" do
|
225
|
+
@query.rows('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize
|
226
|
+
@query.rows.should == [:hierarchize, ['[Customers].[Country].Members', '[Customers].[City].Members']]
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should hierarchize last set of crossjoin" do
|
230
|
+
@query.rows('[Product].children').crossjoin('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize
|
231
|
+
@query.rows.should == [:crossjoin, ['[Product].children'],
|
232
|
+
[:hierarchize, ['[Customers].[Country].Members', '[Customers].[City].Members']]]
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should hierarchize last set of nonempty_crossjoin" do
|
236
|
+
@query.rows('[Product].children').nonempty_crossjoin('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize
|
237
|
+
@query.rows.should == [:nonempty_crossjoin, ['[Product].children'],
|
238
|
+
[:hierarchize, ['[Customers].[Country].Members', '[Customers].[City].Members']]]
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should hierarchize all crossjoin" do
|
242
|
+
@query.rows('[Product].children').crossjoin('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize_all
|
243
|
+
@query.rows.should == [:hierarchize, [:crossjoin, ['[Product].children'],
|
244
|
+
['[Customers].[Country].Members', '[Customers].[City].Members']]]
|
245
|
+
end
|
246
|
+
|
247
|
+
it "should hierarchize with POST" do
|
248
|
+
@query.rows('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize(:post)
|
249
|
+
@query.rows.should == [:hierarchize, ['[Customers].[Country].Members', '[Customers].[City].Members'], 'POST']
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
describe "except" do
|
255
|
+
it "should except one set from other" do
|
256
|
+
@query.rows('[Customers].[Country].Members').except('[Customers].[USA]')
|
257
|
+
@query.rows.should == [:except, ['[Customers].[Country].Members'], ['[Customers].[USA]']]
|
258
|
+
end
|
259
|
+
|
260
|
+
it "should except from last set of crossjoin" do
|
261
|
+
@query.rows('[Product].children').crossjoin('[Customers].[Country].Members').except('[Customers].[USA]')
|
262
|
+
@query.rows.should == [:crossjoin, ['[Product].children'],
|
263
|
+
[:except, ['[Customers].[Country].Members'], ['[Customers].[USA]']]]
|
264
|
+
end
|
265
|
+
|
266
|
+
it "should except from last set of nonempty_crossjoin" do
|
267
|
+
@query.rows('[Product].children').nonempty_crossjoin('[Customers].[Country].Members').except('[Customers].[USA]')
|
268
|
+
@query.rows.should == [:nonempty_crossjoin, ['[Product].children'],
|
269
|
+
[:except, ['[Customers].[Country].Members'], ['[Customers].[USA]']]]
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
describe "filter" do
|
274
|
+
it "should filter set by condition" do
|
275
|
+
@query.rows('[Customers].[Country].Members').filter('[Measures].[Unit Sales] > 1000')
|
276
|
+
@query.rows.should == [:filter, ['[Customers].[Country].Members'], '[Measures].[Unit Sales] > 1000']
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should filter using set alias" do
|
280
|
+
@query.rows('[Customers].[Country].Members').filter('NOT ISEMPTY(S.CURRENT)', :as => 'S')
|
281
|
+
@query.rows.should == [:filter, ['[Customers].[Country].Members'], 'NOT ISEMPTY(S.CURRENT)', 'S']
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
describe "where" do
|
286
|
+
it "should accept conditions" do
|
287
|
+
@query.where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').should equal(@query)
|
288
|
+
@query.where.should == ['[Time].[2010].[Q1]', '[Customers].[USA].[CA]']
|
289
|
+
end
|
290
|
+
|
291
|
+
it "should accept conditions as array" do
|
292
|
+
@query.where(['[Time].[2010].[Q1]', '[Customers].[USA].[CA]'])
|
293
|
+
@query.where.should == ['[Time].[2010].[Q1]', '[Customers].[USA].[CA]']
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should accept conditions with several method calls" do
|
297
|
+
@query.where('[Time].[2010].[Q1]').where('[Customers].[USA].[CA]')
|
298
|
+
@query.where.should == ['[Time].[2010].[Q1]', '[Customers].[USA].[CA]']
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should do crossjoin of where conditions" do
|
302
|
+
@query.where('[Customers].[USA]').crossjoin('[Time].[2011].[Q1]', '[Time].[2011].[Q2]')
|
303
|
+
@query.where.should == [:crossjoin, ['[Customers].[USA]'], ['[Time].[2011].[Q1]', '[Time].[2011].[Q2]']]
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should do nonempty_crossjoin of where conditions" do
|
307
|
+
@query.where('[Customers].[USA]').nonempty_crossjoin('[Time].[2011].[Q1]', '[Time].[2011].[Q2]')
|
308
|
+
@query.where.should == [:nonempty_crossjoin, ['[Customers].[USA]'], ['[Time].[2011].[Q1]', '[Time].[2011].[Q2]']]
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
describe "with member" do
|
313
|
+
it "should accept definition" do
|
314
|
+
@query.with_member('[Measures].[ProfitPct]').
|
315
|
+
as('Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])').
|
316
|
+
should equal(@query)
|
317
|
+
@query.with.should == [
|
318
|
+
[ :member, '[Measures].[ProfitPct]',
|
319
|
+
'Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])'
|
320
|
+
]
|
321
|
+
]
|
322
|
+
end
|
323
|
+
|
324
|
+
it "should accept definition with additional parameters" do
|
325
|
+
@query.with_member('[Measures].[ProfitPct]').
|
326
|
+
as('Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
|
327
|
+
:solve_order => 1,
|
328
|
+
:format_string => 'Percent')
|
329
|
+
@query.with.should == [
|
330
|
+
[ :member, '[Measures].[ProfitPct]',
|
331
|
+
'Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
|
332
|
+
{:solve_order => 1, :format_string => 'Percent'}
|
333
|
+
]
|
334
|
+
]
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
describe "with set" do
|
339
|
+
it "should accept simple defition" do
|
340
|
+
@query.with_set('SelectedRows').as('[Product].children')
|
341
|
+
@query.with.should == [
|
342
|
+
[ :set, 'SelectedRows',
|
343
|
+
['[Product].children']
|
344
|
+
]
|
345
|
+
]
|
346
|
+
end
|
347
|
+
|
348
|
+
it "should accept definition with crossjoin" do
|
349
|
+
@query.with_set('SelectedRows').as('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]')
|
350
|
+
@query.with.should == [
|
351
|
+
[ :set, 'SelectedRows',
|
352
|
+
[:crossjoin, ['[Product].children'], ['[Customers].[Canada]', '[Customers].[USA]']]
|
353
|
+
]
|
354
|
+
]
|
355
|
+
end
|
356
|
+
|
357
|
+
it "should accept definition with nonempty_crossjoin" do
|
358
|
+
@query.with_set('SelectedRows').as('[Product].children').nonempty_crossjoin('[Customers].[Canada]', '[Customers].[USA]')
|
359
|
+
@query.with.should == [
|
360
|
+
[ :set, 'SelectedRows',
|
361
|
+
[:nonempty_crossjoin, ['[Product].children'], ['[Customers].[Canada]', '[Customers].[USA]']]
|
362
|
+
]
|
363
|
+
]
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
describe "to MDX" do
|
368
|
+
it "should return MDX query" do
|
369
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
370
|
+
rows('[Product].children').
|
371
|
+
where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
|
372
|
+
to_mdx.should be_like <<-SQL
|
373
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
374
|
+
[Product].children ON ROWS
|
375
|
+
FROM [Sales]
|
376
|
+
WHERE ([Time].[2010].[Q1], [Customers].[USA].[CA])
|
377
|
+
SQL
|
378
|
+
end
|
379
|
+
|
380
|
+
it "should return query with crossjoin" do
|
381
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
382
|
+
rows('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
|
383
|
+
where('[Time].[2010].[Q1]').
|
384
|
+
to_mdx.should be_like <<-SQL
|
385
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
386
|
+
CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]}) ON ROWS
|
387
|
+
FROM [Sales]
|
388
|
+
WHERE ([Time].[2010].[Q1])
|
389
|
+
SQL
|
390
|
+
end
|
391
|
+
|
392
|
+
it "should return query with several crossjoins" do
|
393
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
394
|
+
rows('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
|
395
|
+
crossjoin('[Time].[2010].[Q1]', '[Time].[2010].[Q2]').
|
396
|
+
to_mdx.should be_like <<-SQL
|
397
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
398
|
+
CROSSJOIN(CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]}),
|
399
|
+
{[Time].[2010].[Q1], [Time].[2010].[Q2]}) ON ROWS
|
400
|
+
FROM [Sales]
|
401
|
+
SQL
|
402
|
+
end
|
403
|
+
|
404
|
+
it "should return query with crossjoin and nonempty" do
|
405
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
406
|
+
rows('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').nonempty.
|
407
|
+
where('[Time].[2010].[Q1]').
|
408
|
+
to_mdx.should be_like <<-SQL
|
409
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
410
|
+
NON EMPTY CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]}) ON ROWS
|
411
|
+
FROM [Sales]
|
412
|
+
WHERE ([Time].[2010].[Q1])
|
413
|
+
SQL
|
414
|
+
end
|
415
|
+
|
416
|
+
it "should return query with nonempty_crossjoin" do
|
417
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
418
|
+
rows('[Product].children').nonempty_crossjoin('[Customers].[Canada]', '[Customers].[USA]').
|
419
|
+
where('[Time].[2010].[Q1]').
|
420
|
+
to_mdx.should be_like <<-SQL
|
421
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
422
|
+
NONEMPTYCROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]}) ON ROWS
|
423
|
+
FROM [Sales]
|
424
|
+
WHERE ([Time].[2010].[Q1])
|
425
|
+
SQL
|
426
|
+
end
|
427
|
+
|
428
|
+
it "should return query with where with several same dimension members" do
|
429
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
430
|
+
rows('[Product].children').
|
431
|
+
where('[Customers].[Canada]', '[Customers].[USA]').
|
432
|
+
to_mdx.should be_like <<-SQL
|
433
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
434
|
+
[Product].children ON ROWS
|
435
|
+
FROM [Sales]
|
436
|
+
WHERE {[Customers].[Canada], [Customers].[USA]}
|
437
|
+
SQL
|
438
|
+
end
|
439
|
+
|
440
|
+
it "should return query with where with several different dimension members returned by function" do
|
441
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
442
|
+
rows('[Product].children').
|
443
|
+
where('Head([Customers].Members).Item(0)', 'Head([Gender].Members).Item(0)').
|
444
|
+
to_mdx.should be_like <<-SQL
|
445
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
446
|
+
[Product].children ON ROWS
|
447
|
+
FROM [Sales]
|
448
|
+
WHERE (Head([Customers].Members).Item(0), Head([Gender].Members).Item(0))
|
449
|
+
SQL
|
450
|
+
end
|
451
|
+
|
452
|
+
it "should return query with where with crossjoin" do
|
453
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
454
|
+
rows('[Product].children').
|
455
|
+
where('[Customers].[USA]').crossjoin('[Time].[2011].[Q1]', '[Time].[2011].[Q2]').
|
456
|
+
to_mdx.should be_like <<-SQL
|
457
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
458
|
+
[Product].children ON ROWS
|
459
|
+
FROM [Sales]
|
460
|
+
WHERE CROSSJOIN({[Customers].[USA]}, {[Time].[2011].[Q1], [Time].[2011].[Q2]})
|
461
|
+
SQL
|
462
|
+
end
|
463
|
+
|
464
|
+
it "should return query with where with nonempty_crossjoin" do
|
465
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
466
|
+
rows('[Product].children').
|
467
|
+
where('[Customers].[USA]').nonempty_crossjoin('[Time].[2011].[Q1]', '[Time].[2011].[Q2]').
|
468
|
+
to_mdx.should be_like <<-SQL
|
469
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
470
|
+
[Product].children ON ROWS
|
471
|
+
FROM [Sales]
|
472
|
+
WHERE NONEMPTYCROSSJOIN({[Customers].[USA]}, {[Time].[2011].[Q1], [Time].[2011].[Q2]})
|
473
|
+
SQL
|
474
|
+
end
|
475
|
+
|
476
|
+
it "should return query with order by one measure" do
|
477
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
478
|
+
rows('[Product].children').order('[Measures].[Unit Sales]', :bdesc).
|
479
|
+
to_mdx.should be_like <<-SQL
|
480
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
481
|
+
ORDER([Product].children, [Measures].[Unit Sales], BDESC) ON ROWS
|
482
|
+
FROM [Sales]
|
483
|
+
SQL
|
484
|
+
end
|
485
|
+
|
486
|
+
it "should return query with order by measure and other member" do
|
487
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
488
|
+
rows('[Product].children').order(['[Measures].[Unit Sales]', '[Customers].[USA]'], :asc).
|
489
|
+
to_mdx.should be_like <<-SQL
|
490
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
491
|
+
ORDER([Product].children, ([Measures].[Unit Sales], [Customers].[USA]), ASC) ON ROWS
|
492
|
+
FROM [Sales]
|
493
|
+
SQL
|
494
|
+
end
|
495
|
+
|
496
|
+
%w(top bottom).each do |extreme|
|
497
|
+
it "should return query with #{extreme} count by one measure" do
|
498
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
499
|
+
rows('[Product].children').send(:"#{extreme}_count", 5, '[Measures].[Unit Sales]').
|
500
|
+
to_mdx.should be_like <<-SQL
|
501
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
502
|
+
#{extreme.upcase}COUNT([Product].children, 5, [Measures].[Unit Sales]) ON ROWS
|
503
|
+
FROM [Sales]
|
504
|
+
SQL
|
505
|
+
end
|
506
|
+
|
507
|
+
it "should return query with #{extreme} count without measure" do
|
508
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
509
|
+
rows('[Product].children').send(:"#{extreme}_count", 5).
|
510
|
+
to_mdx.should be_like <<-SQL
|
511
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
512
|
+
#{extreme.upcase}COUNT([Product].children, 5) ON ROWS
|
513
|
+
FROM [Sales]
|
514
|
+
SQL
|
515
|
+
end
|
516
|
+
|
517
|
+
it "should return query with #{extreme} count by measure and other member" do
|
518
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
519
|
+
rows('[Product].children').send(:"#{extreme}_count", 5, ['[Measures].[Unit Sales]', '[Customers].[USA]']).
|
520
|
+
to_mdx.should be_like <<-SQL
|
521
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
522
|
+
#{extreme.upcase}COUNT([Product].children, 5, ([Measures].[Unit Sales], [Customers].[USA])) ON ROWS
|
523
|
+
FROM [Sales]
|
524
|
+
SQL
|
525
|
+
end
|
526
|
+
|
527
|
+
it "should return query with #{extreme} percent by one measure" do
|
528
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
529
|
+
rows('[Product].children').send(:"#{extreme}_percent", 20, '[Measures].[Unit Sales]').
|
530
|
+
to_mdx.should be_like <<-SQL
|
531
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
532
|
+
#{extreme.upcase}PERCENT([Product].children, 20, [Measures].[Unit Sales]) ON ROWS
|
533
|
+
FROM [Sales]
|
534
|
+
SQL
|
535
|
+
end
|
536
|
+
|
537
|
+
it "should return query with #{extreme} sum by one measure" do
|
538
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
539
|
+
rows('[Product].children').send(:"#{extreme}_sum", 1000, '[Measures].[Unit Sales]').
|
540
|
+
to_mdx.should be_like <<-SQL
|
541
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
542
|
+
#{extreme.upcase}SUM([Product].children, 1000, [Measures].[Unit Sales]) ON ROWS
|
543
|
+
FROM [Sales]
|
544
|
+
SQL
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
it "should return query with hierarchize" do
|
549
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
550
|
+
rows('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize.
|
551
|
+
to_mdx.should be_like <<-SQL
|
552
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
553
|
+
HIERARCHIZE({[Customers].[Country].Members, [Customers].[City].Members}) ON ROWS
|
554
|
+
FROM [Sales]
|
555
|
+
SQL
|
556
|
+
end
|
557
|
+
|
558
|
+
it "should return query with hierarchize and order" do
|
559
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
560
|
+
rows('[Customers].[Country].Members', '[Customers].[City].Members').hierarchize(:post).
|
561
|
+
to_mdx.should be_like <<-SQL
|
562
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
563
|
+
HIERARCHIZE({[Customers].[Country].Members, [Customers].[City].Members}, POST) ON ROWS
|
564
|
+
FROM [Sales]
|
565
|
+
SQL
|
566
|
+
end
|
567
|
+
|
568
|
+
it "should return query with except" do
|
569
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
570
|
+
rows('[Customers].[Country].Members').except('[Customers].[USA]').
|
571
|
+
to_mdx.should be_like <<-SQL
|
572
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
573
|
+
EXCEPT([Customers].[Country].Members, {[Customers].[USA]}) ON ROWS
|
574
|
+
FROM [Sales]
|
575
|
+
SQL
|
576
|
+
end
|
577
|
+
|
578
|
+
it "should return query with filter" do
|
579
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
580
|
+
rows('[Customers].[Country].Members').filter('[Measures].[Unit Sales] > 1000').
|
581
|
+
to_mdx.should be_like <<-SQL
|
582
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
583
|
+
FILTER([Customers].[Country].Members, [Measures].[Unit Sales] > 1000) ON ROWS
|
584
|
+
FROM [Sales]
|
585
|
+
SQL
|
586
|
+
end
|
587
|
+
|
588
|
+
it "should return query with filter and set alias" do
|
589
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
590
|
+
rows('[Customers].[Country].Members').filter('NOT ISEMPTY(S.CURRENT)', :as => 'S').
|
591
|
+
to_mdx.should be_like <<-SQL
|
592
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
593
|
+
FILTER([Customers].[Country].Members AS S, NOT ISEMPTY(S.CURRENT)) ON ROWS
|
594
|
+
FROM [Sales]
|
595
|
+
SQL
|
596
|
+
end
|
597
|
+
|
598
|
+
it "should return query with filter non-empty" do
|
599
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
600
|
+
rows('[Customers].[Country].Members').filter_nonempty.
|
601
|
+
to_mdx.should be_like <<-SQL
|
602
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
603
|
+
FILTER([Customers].[Country].Members AS S, NOT ISEMPTY(S.CURRENT)) ON ROWS
|
604
|
+
FROM [Sales]
|
605
|
+
SQL
|
606
|
+
end
|
607
|
+
|
608
|
+
it "should return query including WITH MEMBER clause" do
|
609
|
+
@query.
|
610
|
+
with_member('[Measures].[ProfitPct]').
|
611
|
+
as('Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
|
612
|
+
:solve_order => 1, :format_string => 'Percent').
|
613
|
+
with_member('[Measures].[ProfitValue]').
|
614
|
+
as('[Measures].[Store Sales] * [Measures].[ProfitPct]',
|
615
|
+
:solve_order => 2, :format_string => 'Currency').
|
616
|
+
columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
617
|
+
rows('[Product].children').
|
618
|
+
where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
|
619
|
+
to_mdx.should be_like <<-SQL
|
620
|
+
WITH
|
621
|
+
MEMBER [Measures].[ProfitPct] AS
|
622
|
+
'Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
|
623
|
+
SOLVE_ORDER = 1, FORMAT_STRING = 'Percent'
|
624
|
+
MEMBER [Measures].[ProfitValue] AS
|
625
|
+
'[Measures].[Store Sales] * [Measures].[ProfitPct]',
|
626
|
+
SOLVE_ORDER = 2, FORMAT_STRING = 'Currency'
|
627
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
628
|
+
[Product].children ON ROWS
|
629
|
+
FROM [Sales]
|
630
|
+
WHERE ([Time].[2010].[Q1], [Customers].[USA].[CA])
|
631
|
+
SQL
|
632
|
+
end
|
633
|
+
|
634
|
+
it "should return query including WITH SET clause" do
|
635
|
+
@query.with_set('SelectedRows').
|
636
|
+
as('[Product].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
|
637
|
+
with_member('[Measures].[Profit]').
|
638
|
+
as('[Measures].[Store Sales] - [Measures].[Store Cost]').
|
639
|
+
columns('[Measures].[Profit]').
|
640
|
+
rows('SelectedRows').
|
641
|
+
to_mdx.should be_like <<-SQL
|
642
|
+
WITH
|
643
|
+
SET SelectedRows AS
|
644
|
+
'CROSSJOIN([Product].children, {[Customers].[Canada], [Customers].[USA]})'
|
645
|
+
MEMBER [Measures].[Profit] AS
|
646
|
+
'[Measures].[Store Sales] - [Measures].[Store Cost]'
|
647
|
+
SELECT {[Measures].[Profit]} ON COLUMNS,
|
648
|
+
SelectedRows ON ROWS
|
649
|
+
FROM [Sales]
|
650
|
+
SQL
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
describe "execute" do
|
655
|
+
it "should return result" do
|
656
|
+
result = @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
657
|
+
rows('[Product].children').
|
658
|
+
where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
|
659
|
+
execute
|
660
|
+
result.values.should == sql_select_numbers(@sql_select)
|
661
|
+
end
|
662
|
+
end
|
663
|
+
|
664
|
+
describe "result HTML formatting" do
|
665
|
+
it "should format result" do
|
666
|
+
result = @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
667
|
+
rows('[Product].children').
|
668
|
+
where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
|
669
|
+
execute
|
670
|
+
Nokogiri::HTML.fragment(result.to_html).css('tr').size.should == (sql_select_numbers(@sql_select).size + 1)
|
671
|
+
end
|
672
|
+
|
673
|
+
# it "test" do
|
674
|
+
# puts @olap.from('Sales').
|
675
|
+
# columns('[Product].children').
|
676
|
+
# rows('[Customers].[USA].[CA].children').
|
677
|
+
# where('[Time].[2010].[Q1]', '[Measures].[Store Sales]').
|
678
|
+
# execute.to_html
|
679
|
+
# end
|
680
|
+
end
|
681
|
+
|
682
|
+
end
|
683
|
+
|
684
|
+
describe "errors" do
|
685
|
+
before(:each) do
|
686
|
+
@query = @olap.from('Sales')
|
687
|
+
end
|
688
|
+
|
689
|
+
it "should raise error when invalid MDX statement" do
|
690
|
+
expect {
|
691
|
+
@olap.execute "SELECT dummy FROM"
|
692
|
+
}.to raise_error {|e|
|
693
|
+
e.should be_kind_of(Mondrian::OLAP::Error)
|
694
|
+
e.message.should == 'org.olap4j.OlapException: mondrian gave exception while parsing query'
|
695
|
+
e.root_cause_message.should == "Syntax error at line 1, column 14, token 'FROM'"
|
696
|
+
}
|
697
|
+
end
|
698
|
+
|
699
|
+
it "should raise error when invalid MDX object" do
|
700
|
+
expect {
|
701
|
+
@query.columns('[Measures].[Dummy]').execute
|
702
|
+
}.to raise_error {|e|
|
703
|
+
e.should be_kind_of(Mondrian::OLAP::Error)
|
704
|
+
e.message.should == 'org.olap4j.OlapException: mondrian gave exception while parsing query'
|
705
|
+
e.root_cause_message.should == "MDX object '[Measures].[Dummy]' not found in cube 'Sales'"
|
706
|
+
}
|
707
|
+
end
|
708
|
+
|
709
|
+
it "should raise error when invalid formula" do
|
710
|
+
expect {
|
711
|
+
@query.with_member('[Measures].[Dummy]').as('Dummy(123)').
|
712
|
+
columns('[Measures].[Dummy]').execute
|
713
|
+
}.to raise_error {|e|
|
714
|
+
e.should be_kind_of(Mondrian::OLAP::Error)
|
715
|
+
e.message.should == 'org.olap4j.OlapException: mondrian gave exception while parsing query'
|
716
|
+
e.root_cause_message.should == "No function matches signature 'Dummy(<Numeric Expression>)'"
|
717
|
+
}
|
718
|
+
end
|
719
|
+
|
720
|
+
end
|
721
|
+
|
722
|
+
describe "drill through" do
|
723
|
+
before(:all) do
|
724
|
+
@query = @olap.from('Sales')
|
725
|
+
@result = @query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
726
|
+
rows('[Product].children').
|
727
|
+
where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
|
728
|
+
execute
|
729
|
+
@drill_through = @result.drill_through(:row => 0, :column => 0)
|
730
|
+
end
|
731
|
+
|
732
|
+
it "should return column types" do
|
733
|
+
@drill_through.column_types.should == [
|
734
|
+
:INT, :VARCHAR, :INT, :INT, :INT,
|
735
|
+
:VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR, :VARCHAR,
|
736
|
+
:VARCHAR, :VARCHAR, :VARCHAR, :INT,
|
737
|
+
:VARCHAR,
|
738
|
+
:DECIMAL
|
739
|
+
]
|
740
|
+
end if MONDRIAN_DRIVER == 'mysql'
|
741
|
+
|
742
|
+
it "should return column names" do
|
743
|
+
# ignore calculated customer full name column name which is shown differently on each database
|
744
|
+
@drill_through.column_names[0..12].should == %w(
|
745
|
+
the_year quarter month_of_year week_of_year day_of_month
|
746
|
+
product_family product_department product_category product_subcategory brand_name product_name
|
747
|
+
state_province city
|
748
|
+
)
|
749
|
+
@drill_through.column_names[14..16].should == %w(
|
750
|
+
id gender unit_sales
|
751
|
+
)
|
752
|
+
end if %w(mysql postgresql).include? MONDRIAN_DRIVER
|
753
|
+
|
754
|
+
it "should return table names" do
|
755
|
+
@drill_through.table_names.should == [
|
756
|
+
"time", "time", "time", "time", "time",
|
757
|
+
"product_classes", "product_classes", "product_classes", "product_classes", "products", "products",
|
758
|
+
"customers", "customers", "", "customers",
|
759
|
+
"customers",
|
760
|
+
"sales"
|
761
|
+
]
|
762
|
+
end if %w(mysql postgresql).include? MONDRIAN_DRIVER
|
763
|
+
|
764
|
+
it "should return column labels" do
|
765
|
+
@drill_through.column_labels.should == [
|
766
|
+
"Year", "Quarter", "Month", "Week", "Day",
|
767
|
+
"Product Family", "Product Department", "Product Category", "Product Subcategory", "Brand Name", "Product Name",
|
768
|
+
"State Province", "City", "Name", "Name (Key)",
|
769
|
+
"Gender",
|
770
|
+
"Unit Sales"
|
771
|
+
]
|
772
|
+
end
|
773
|
+
|
774
|
+
it "should return row values" do
|
775
|
+
@drill_through.rows.size.should == 15 # number of generated test rows
|
776
|
+
end
|
777
|
+
|
778
|
+
it "should return correct row value types" do
|
779
|
+
@drill_through.rows.first.map(&:class).should ==
|
780
|
+
case MONDRIAN_DRIVER
|
781
|
+
when "oracle"
|
782
|
+
[
|
783
|
+
BigDecimal, String, BigDecimal, BigDecimal, BigDecimal,
|
784
|
+
String, String, String, String, String, String,
|
785
|
+
String, String, String, BigDecimal,
|
786
|
+
String,
|
787
|
+
BigDecimal
|
788
|
+
]
|
789
|
+
else
|
790
|
+
[
|
791
|
+
Fixnum, String, Fixnum, Fixnum, Fixnum,
|
792
|
+
String, String, String, String, String, String,
|
793
|
+
String, String, String, Fixnum,
|
794
|
+
String,
|
795
|
+
BigDecimal
|
796
|
+
]
|
797
|
+
end
|
798
|
+
end
|
799
|
+
|
800
|
+
it "should return only specified max rows" do
|
801
|
+
drill_through = @result.drill_through(:row => 0, :column => 0, :max_rows => 10)
|
802
|
+
drill_through.rows.size.should == 10
|
803
|
+
end
|
804
|
+
end
|
805
|
+
|
806
|
+
|
807
|
+
end
|