mondrian-olap 0.4.0-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|