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.
Files changed (46) hide show
  1. data/.rspec +2 -0
  2. data/Changelog.md +60 -0
  3. data/Gemfile +21 -0
  4. data/LICENSE-Mondrian.html +259 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +302 -0
  7. data/RUNNING_TESTS.rdoc +66 -0
  8. data/Rakefile +48 -0
  9. data/VERSION +1 -0
  10. data/lib/mondrian-olap.rb +1 -0
  11. data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
  12. data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
  13. data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
  14. data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
  15. data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
  16. data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
  17. data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
  18. data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
  19. data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
  20. data/lib/mondrian/jars/javacup.jar +0 -0
  21. data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
  22. data/lib/mondrian/jars/log4j.properties +5 -0
  23. data/lib/mondrian/jars/mondrian.jar +0 -0
  24. data/lib/mondrian/jars/olap4j.jar +0 -0
  25. data/lib/mondrian/olap.rb +17 -0
  26. data/lib/mondrian/olap/connection.rb +201 -0
  27. data/lib/mondrian/olap/cube.rb +297 -0
  28. data/lib/mondrian/olap/error.rb +57 -0
  29. data/lib/mondrian/olap/query.rb +342 -0
  30. data/lib/mondrian/olap/result.rb +264 -0
  31. data/lib/mondrian/olap/schema.rb +378 -0
  32. data/lib/mondrian/olap/schema_element.rb +153 -0
  33. data/lib/mondrian/olap/schema_udf.rb +282 -0
  34. data/mondrian-olap.gemspec +128 -0
  35. data/spec/connection_role_spec.rb +130 -0
  36. data/spec/connection_spec.rb +72 -0
  37. data/spec/cube_spec.rb +318 -0
  38. data/spec/fixtures/MondrianTest.xml +134 -0
  39. data/spec/fixtures/MondrianTestOracle.xml +134 -0
  40. data/spec/mondrian_spec.rb +53 -0
  41. data/spec/query_spec.rb +807 -0
  42. data/spec/rake_tasks.rb +260 -0
  43. data/spec/schema_definition_spec.rb +1249 -0
  44. data/spec/spec_helper.rb +134 -0
  45. data/spec/support/matchers/be_like.rb +24 -0
  46. metadata +278 -0
@@ -0,0 +1,260 @@
1
+ namespace :db do
2
+ task :require_spec_helper do
3
+ require File.expand_path("../spec_helper", __FILE__)
4
+ end
5
+
6
+ desc "Create test database tables"
7
+ task :create_tables => :require_spec_helper do
8
+ puts "==> Creating tables for test data"
9
+ ActiveRecord::Schema.define do
10
+
11
+ create_table :time, :force => true do |t|
12
+ t.datetime :the_date
13
+ t.string :the_day, :limit => 30
14
+ t.string :the_month, :limit => 30
15
+ t.integer :the_year
16
+ t.integer :day_of_month
17
+ t.integer :week_of_year
18
+ t.integer :month_of_year
19
+ t.string :quarter, :limit => 30
20
+ end
21
+
22
+ create_table :products, :force => true do |t|
23
+ t.integer :product_class_id
24
+ t.string :brand_name, :limit => 60
25
+ t.string :product_name, :limit => 60
26
+ end
27
+
28
+ create_table :product_classes, :force => true do |t|
29
+ t.string :product_subcategory, :limit => 30
30
+ t.string :product_category, :limit => 30
31
+ t.string :product_department, :limit => 30
32
+ t.string :product_family, :limit => 30
33
+ end
34
+
35
+ create_table :customers, :force => true do |t|
36
+ t.string :country, :limit => 30
37
+ t.string :state_province, :limit => 30
38
+ t.string :city, :limit => 30
39
+ t.string :fname, :limit => 30
40
+ t.string :lname, :limit => 30
41
+ t.string :fullname, :limit => 60
42
+ t.string :gender, :limit => 30
43
+ end
44
+
45
+ create_table :sales, :force => true, :id => false do |t|
46
+ t.integer :product_id
47
+ t.integer :time_id
48
+ t.integer :customer_id
49
+ t.decimal :store_sales, :precision => 10, :scale => 4
50
+ t.decimal :store_cost, :precision => 10, :scale => 4
51
+ t.decimal :unit_sales, :precision => 10, :scale => 4
52
+ end
53
+ end
54
+ end
55
+
56
+ task :setup_luciddb => :require_spec_helper do
57
+ # create link to mysql database to import tables
58
+ # see description at http://pub.eigenbase.org/wiki/LucidDbCreateForeignServer
59
+ if MONDRIAN_DRIVER == 'luciddb'
60
+ conn = ActiveRecord::Base.connection
61
+ conn.execute "drop schema mondrian_test_source cascade" rescue nil
62
+ conn.execute "drop server mondrian_test_source" rescue nil
63
+ conn.execute "create schema mondrian_test_source"
64
+ conn.execute <<-SQL
65
+ create server mondrian_test_source
66
+ foreign data wrapper sys_jdbc
67
+ options(
68
+ driver_class 'com.mysql.jdbc.Driver',
69
+ url 'jdbc:mysql://localhost/mondrian_test?characterEncoding=utf-8&useCursorFetch=true',
70
+ user_name 'mondrian_test',
71
+ password 'mondrian_test',
72
+ login_timeout '10',
73
+ fetch_size '1000',
74
+ validation_query 'select 1',
75
+ schema_name 'MONDRIAN_TEST',
76
+ table_types 'TABLE')
77
+ SQL
78
+ conn.execute "import foreign schema mondrian_test from server mondrian_test_source into mondrian_test_source"
79
+ end
80
+ end
81
+
82
+ task :define_models => :require_spec_helper do
83
+ unless MONDRIAN_DRIVER == 'luciddb'
84
+ class TimeDimension < ActiveRecord::Base
85
+ self.table_name = "time"
86
+ validates_presence_of :the_date
87
+ before_create do
88
+ self.the_day = the_date.strftime("%A")
89
+ self.the_month = the_date.strftime("%B")
90
+ self.the_year = the_date.strftime("%Y").to_i
91
+ self.day_of_month = the_date.strftime("%d").to_i
92
+ self.week_of_year = the_date.strftime("%W").to_i
93
+ self.month_of_year = the_date.strftime("%m").to_i
94
+ self.quarter = "Q#{(month_of_year-1)/3+1}"
95
+ end
96
+ end
97
+ class Product < ActiveRecord::Base
98
+ belongs_to :product_class
99
+ end
100
+ class ProductClass < ActiveRecord::Base
101
+ end
102
+ class Customer < ActiveRecord::Base
103
+ end
104
+ class Sales < ActiveRecord::Base
105
+ self.table_name = "sales"
106
+ belongs_to :time_by_day
107
+ belongs_to :product
108
+ belongs_to :customer
109
+ end
110
+ end
111
+ end
112
+
113
+ desc "Create test data"
114
+ task :create_data => [:create_tables, :setup_luciddb, :create_time_data, :create_product_data, :create_customer_data, :create_sales_data]
115
+
116
+ task :create_time_data => :define_models do
117
+ puts "==> Creating time dimension"
118
+ if MONDRIAN_DRIVER == 'luciddb'
119
+ ActiveRecord::Base.connection.execute 'truncate table "TIME"'
120
+ ActiveRecord::Base.connection.execute 'insert into "TIME" select * from mondrian_test_source."time"'
121
+ ActiveRecord::Base.connection.execute 'analyze table "TIME" compute statistics for all columns'
122
+ else
123
+ TimeDimension.delete_all
124
+ start_time = Time.local(2010,1,1)
125
+ (2*365).times do |i|
126
+ TimeDimension.create!(:the_date => start_time + i.day)
127
+ end
128
+ end
129
+ end
130
+
131
+ task :create_product_data => :define_models do
132
+ puts "==> Creating product data"
133
+ if MONDRIAN_DRIVER == 'luciddb'
134
+ ActiveRecord::Base.connection.execute 'truncate table product_classes'
135
+ ActiveRecord::Base.connection.execute 'truncate table products'
136
+ ActiveRecord::Base.connection.execute 'insert into product_classes select * from mondrian_test_source."product_classes"'
137
+ ActiveRecord::Base.connection.execute 'insert into products select * from mondrian_test_source."products"'
138
+ ActiveRecord::Base.connection.execute 'analyze table product_classes compute statistics for all columns'
139
+ ActiveRecord::Base.connection.execute 'analyze table products compute statistics for all columns'
140
+ else
141
+ Product.delete_all
142
+ ProductClass.delete_all
143
+ families = ["Drink", "Food", "Non-Consumable"]
144
+ (1..100).each do |i|
145
+ product_class = ProductClass.create!(
146
+ :product_family => families[i % 3],
147
+ :product_department => "Department #{i}",
148
+ :product_category => "Category #{i}",
149
+ :product_subcategory => "Subcategory #{i}"
150
+ )
151
+ Product.create!(
152
+ # LucidDB is not returning inserted ID therefore doing it hard way
153
+ :product_class_id => ProductClass.find_all_by_product_category("Category #{i}").first.id,
154
+ :brand_name => "Brand #{i}",
155
+ :product_name => "Product #{i}"
156
+ )
157
+ end
158
+ end
159
+ end
160
+
161
+ task :create_customer_data => :define_models do
162
+ puts "==> Creating customer data"
163
+ if MONDRIAN_DRIVER == 'luciddb'
164
+ ActiveRecord::Base.connection.execute 'truncate table customers'
165
+ ActiveRecord::Base.connection.execute 'insert into customers select * from mondrian_test_source."customers"'
166
+ ActiveRecord::Base.connection.execute 'analyze table customers compute statistics for all columns'
167
+ else
168
+ Customer.delete_all
169
+ i = 0
170
+ [
171
+ ["Canada", "BC", "Burnaby"],["Canada", "BC", "Cliffside"],["Canada", "BC", "Haney"],["Canada", "BC", "Ladner"],
172
+ ["Canada", "BC", "Langford"],["Canada", "BC", "Langley"],["Canada", "BC", "Metchosin"],["Canada", "BC", "N. Vancouver"],
173
+ ["Canada", "BC", "Newton"],["Canada", "BC", "Oak Bay"],["Canada", "BC", "Port Hammond"],["Canada", "BC", "Richmond"],
174
+ ["Canada", "BC", "Royal Oak"],["Canada", "BC", "Shawnee"],["Canada", "BC", "Sooke"],["Canada", "BC", "Vancouver"],
175
+ ["Canada", "BC", "Victoria"],["Canada", "BC", "Westminster"],
176
+ ["Mexico", "DF", "San Andres"],["Mexico", "DF", "Santa Anita"],["Mexico", "DF", "Santa Fe"],["Mexico", "DF", "Tixapan"],
177
+ ["Mexico", "Guerrero", "Acapulco"],["Mexico", "Jalisco", "Guadalajara"],["Mexico", "Mexico", "Mexico City"],
178
+ ["Mexico", "Oaxaca", "Tlaxiaco"],["Mexico", "Sinaloa", "La Cruz"],["Mexico", "Veracruz", "Orizaba"],
179
+ ["Mexico", "Yucatan", "Merida"],["Mexico", "Zacatecas", "Camacho"],["Mexico", "Zacatecas", "Hidalgo"],
180
+ ["USA", "CA", "Altadena"],["USA", "CA", "Arcadia"],["USA", "CA", "Bellflower"],["USA", "CA", "Berkeley"],
181
+ ["USA", "CA", "Beverly Hills"],["USA", "CA", "Burbank"],["USA", "CA", "Burlingame"],["USA", "CA", "Chula Vista"],
182
+ ["USA", "CA", "Colma"],["USA", "CA", "Concord"],["USA", "CA", "Coronado"],["USA", "CA", "Daly City"],
183
+ ["USA", "CA", "Downey"],["USA", "CA", "El Cajon"],["USA", "CA", "Fremont"],["USA", "CA", "Glendale"],
184
+ ["USA", "CA", "Grossmont"],["USA", "CA", "Imperial Beach"],["USA", "CA", "La Jolla"],["USA", "CA", "La Mesa"],
185
+ ["USA", "CA", "Lakewood"],["USA", "CA", "Lemon Grove"],["USA", "CA", "Lincoln Acres"],["USA", "CA", "Long Beach"],
186
+ ["USA", "CA", "Los Angeles"],["USA", "CA", "Mill Valley"],["USA", "CA", "National City"],["USA", "CA", "Newport Beach"],
187
+ ["USA", "CA", "Novato"],["USA", "CA", "Oakland"],["USA", "CA", "Palo Alto"],["USA", "CA", "Pomona"],
188
+ ["USA", "CA", "Redwood City"],["USA", "CA", "Richmond"],["USA", "CA", "San Carlos"],["USA", "CA", "San Diego"],
189
+ ["USA", "CA", "San Francisco"],["USA", "CA", "San Gabriel"],["USA", "CA", "San Jose"],["USA", "CA", "Santa Cruz"],
190
+ ["USA", "CA", "Santa Monica"],["USA", "CA", "Spring Valley"],["USA", "CA", "Torrance"],["USA", "CA", "West Covina"],
191
+ ["USA", "CA", "Woodland Hills"],
192
+ ["USA", "OR", "Albany"],["USA", "OR", "Beaverton"],["USA", "OR", "Corvallis"],["USA", "OR", "Lake Oswego"],
193
+ ["USA", "OR", "Lebanon"],["USA", "OR", "Milwaukie"],["USA", "OR", "Oregon City"],["USA", "OR", "Portland"],
194
+ ["USA", "OR", "Salem"],["USA", "OR", "W. Linn"],["USA", "OR", "Woodburn"],
195
+ ["USA", "WA", "Anacortes"],["USA", "WA", "Ballard"],["USA", "WA", "Bellingham"],["USA", "WA", "Bremerton"],
196
+ ["USA", "WA", "Burien"],["USA", "WA", "Edmonds"],["USA", "WA", "Everett"],["USA", "WA", "Issaquah"],
197
+ ["USA", "WA", "Kirkland"],["USA", "WA", "Lynnwood"],["USA", "WA", "Marysville"],["USA", "WA", "Olympia"],
198
+ ["USA", "WA", "Port Orchard"],["USA", "WA", "Puyallup"],["USA", "WA", "Redmond"],["USA", "WA", "Renton"],
199
+ ["USA", "WA", "Seattle"],["USA", "WA", "Sedro Woolley"],["USA", "WA", "Spokane"],["USA", "WA", "Tacoma"],
200
+ ["USA", "WA", "Walla Walla"],["USA", "WA", "Yakima"]
201
+ ].each do |country, state, city|
202
+ i += 1
203
+ Customer.create!(
204
+ :country => country,
205
+ :state_province => state,
206
+ :city => city,
207
+ :fname => "First#{i}",
208
+ :lname => "Last#{i}",
209
+ :fullname => "First#{i} Last#{i}",
210
+ :gender => i % 2 == 0 ? "M" : "F"
211
+ )
212
+ end
213
+ end
214
+ end
215
+
216
+ task :create_sales_data => :define_models do
217
+ puts "==> Creating sales data"
218
+ if MONDRIAN_DRIVER == 'luciddb'
219
+ ActiveRecord::Base.connection.execute 'truncate table sales'
220
+ ActiveRecord::Base.connection.execute 'insert into sales select * from mondrian_test_source."sales"'
221
+ ActiveRecord::Base.connection.execute 'analyze table sales compute statistics for all columns'
222
+ else
223
+ Sales.delete_all
224
+ count = 100
225
+ # LucidDB does not support LIMIT therefore select all and limit in Ruby
226
+ products = Product.order("id").all[0...count]
227
+ times = TimeDimension.order("id").all[0...count]
228
+ customers = Customer.order("id").all[0...count]
229
+ count.times do |i|
230
+ Sales.create!(
231
+ :product_id => products[i].id,
232
+ :time_id => times[i].id,
233
+ :customer_id => customers[i].id,
234
+ :store_sales => BigDecimal("2#{i}.12"),
235
+ :store_cost => BigDecimal("1#{i}.1234"),
236
+ :unit_sales => i+1
237
+ )
238
+ end
239
+ end
240
+ end
241
+
242
+ end
243
+
244
+ namespace :spec do
245
+ %w(mysql postgresql oracle luciddb mssql sqlserver).each do |driver|
246
+ desc "Run specs with #{driver} driver"
247
+ task driver do
248
+ ENV['MONDRIAN_DRIVER'] = driver
249
+ Rake::Task['spec'].reenable
250
+ Rake::Task['spec'].invoke
251
+ end
252
+ end
253
+
254
+ desc "Run specs with all database drivers"
255
+ task :all do
256
+ %w(mysql postgresql oracle luciddb mssql sqlserver).each do |driver|
257
+ Rake::Task["spec:#{driver}"].invoke
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,1249 @@
1
+ require "spec_helper"
2
+ require "coffee-script"
3
+
4
+ describe "Schema definition" do
5
+
6
+ describe "elements" do
7
+ before(:each) do
8
+ @schema = Mondrian::OLAP::Schema.new
9
+ end
10
+
11
+ describe "root element" do
12
+ it "should render to XML" do
13
+ @schema.to_xml.should be_like <<-XML
14
+ <?xml version="1.0"?>
15
+ <Schema/>
16
+ XML
17
+ end
18
+
19
+ it "should render to XML with attributes" do
20
+ @schema.define('FoodMart') do
21
+ description 'Demo "FoodMart" schema'
22
+ end
23
+ @schema.to_xml.should be_like <<-XML
24
+ <?xml version="1.0"?>
25
+ <Schema description="Demo &quot;FoodMart&quot; schema" name="FoodMart"/>
26
+ XML
27
+ end
28
+
29
+ it "should render to XML using class method" do
30
+ schema = Mondrian::OLAP::Schema.define('FoodMart')
31
+ schema.to_xml.should be_like <<-XML
32
+ <?xml version="1.0"?>
33
+ <Schema name="FoodMart"/>
34
+ XML
35
+ end
36
+ end
37
+
38
+ describe "Cube" do
39
+ it "should render to XML" do
40
+ @schema.define do
41
+ cube 'Sales' do
42
+ default_measure 'Unit Sales'
43
+ description 'Sales cube'
44
+ cache false
45
+ enabled true
46
+ end
47
+ end
48
+ @schema.to_xml.should be_like <<-XML
49
+ <?xml version="1.0"?>
50
+ <Schema name="default">
51
+ <Cube cache="false" defaultMeasure="Unit Sales" description="Sales cube" enabled="true" name="Sales"/>
52
+ </Schema>
53
+ XML
54
+ end
55
+
56
+ it "should render to XML using options hash" do
57
+ @schema.define do
58
+ cube 'Sales', :default_measure => 'Unit Sales',
59
+ :description => 'Sales cube', :cache => false, :enabled => true
60
+ end
61
+ @schema.to_xml.should be_like <<-XML
62
+ <?xml version="1.0"?>
63
+ <Schema name="default">
64
+ <Cube cache="false" defaultMeasure="Unit Sales" description="Sales cube" enabled="true" name="Sales"/>
65
+ </Schema>
66
+ XML
67
+ end
68
+ end
69
+
70
+ describe "Table" do
71
+ it "should render to XML" do
72
+ @schema.define do
73
+ cube 'Sales' do
74
+ table 'sales_fact', :alias => 'sales'
75
+ end
76
+ end
77
+ @schema.to_xml.should be_like <<-XML
78
+ <?xml version="1.0"?>
79
+ <Schema name="default">
80
+ <Cube name="Sales">
81
+ <Table alias="sales" name="sales_fact"/>
82
+ </Cube>
83
+ </Schema>
84
+ XML
85
+ end
86
+
87
+ it "should render table name in uppercase when using Oracle or LucidDB driver" do
88
+ @schema.define do
89
+ cube 'Sales' do
90
+ table 'sales_fact', :alias => 'sales', :schema => 'facts'
91
+ end
92
+ end
93
+ %w(oracle luciddb).each do |driver|
94
+ @schema.to_xml(:driver => driver).should be_like <<-XML
95
+ <?xml version="1.0"?>
96
+ <Schema name="default">
97
+ <Cube name="Sales">
98
+ <Table alias="SALES" name="SALES_FACT" schema="FACTS"/>
99
+ </Cube>
100
+ </Schema>
101
+ XML
102
+ end
103
+ end
104
+
105
+ it "should render table name in uppercase when :upcase_data_dictionary option is set to true" do
106
+ @schema.define :upcase_data_dictionary => true do
107
+ cube 'Sales' do
108
+ table 'sales_fact', :alias => 'sales', :schema => 'facts'
109
+ end
110
+ end
111
+ @schema.to_xml.should be_like <<-XML
112
+ <?xml version="1.0"?>
113
+ <Schema name="default">
114
+ <Cube name="Sales">
115
+ <Table alias="SALES" name="SALES_FACT" schema="FACTS"/>
116
+ </Cube>
117
+ </Schema>
118
+ XML
119
+ end
120
+
121
+ it "should render table name in lowercase when using Oracle or LucidDB driver but with :upcase_data_dictionary set to false" do
122
+ @schema.define :upcase_data_dictionary => false do
123
+ cube 'Sales' do
124
+ table 'sales_fact', :alias => 'sales', :schema => 'facts'
125
+ end
126
+ end
127
+ %w(oracle luciddb).each do |driver|
128
+ @schema.to_xml(:driver => driver).should be_like <<-XML
129
+ <?xml version="1.0"?>
130
+ <Schema name="default">
131
+ <Cube name="Sales">
132
+ <Table alias="sales" name="sales_fact" schema="facts"/>
133
+ </Cube>
134
+ </Schema>
135
+ XML
136
+ end
137
+ end
138
+
139
+ it "should render table with where condition" do
140
+ @schema.define do
141
+ cube 'Sales' do
142
+ table 'sales_fact', :alias => 'sales' do
143
+ sql 'customer_id IS NOT NULL'
144
+ end
145
+ end
146
+ end
147
+ @schema.to_xml.should be_like <<-XML
148
+ <?xml version="1.0"?>
149
+ <Schema name="default">
150
+ <Cube name="Sales">
151
+ <Table alias="sales" name="sales_fact">
152
+ <SQL>customer_id IS NOT NULL</SQL>
153
+ </Table>
154
+ </Cube>
155
+ </Schema>
156
+ XML
157
+ end
158
+ end
159
+
160
+ describe "View" do
161
+ it "should render to XML" do
162
+ @schema.define do
163
+ cube 'Sales' do
164
+ view :alias => 'sales' do
165
+ sql 'select * from sales_fact'
166
+ end
167
+ end
168
+ end
169
+ @schema.to_xml.should be_like <<-XML
170
+ <?xml version="1.0"?>
171
+ <Schema name="default">
172
+ <Cube name="Sales">
173
+ <View alias="sales">
174
+ <SQL>select * from sales_fact</SQL>
175
+ </View>
176
+ </Cube>
177
+ </Schema>
178
+ XML
179
+ end
180
+ end
181
+
182
+ describe "Dimension" do
183
+ it "should render to XML" do
184
+ @schema.define do
185
+ cube 'Sales' do
186
+ dimension 'Gender' do
187
+ foreign_key 'customer_id'
188
+ hierarchy do
189
+ has_all true
190
+ all_member_name 'All Genders'
191
+ primary_key 'customer_id'
192
+ table 'customer'
193
+ level 'Gender', :column => 'gender', :unique_members => true
194
+ end
195
+ end
196
+ end
197
+ end
198
+ @schema.to_xml.should be_like <<-XML
199
+ <?xml version="1.0"?>
200
+ <Schema name="default">
201
+ <Cube name="Sales">
202
+ <Dimension foreignKey="customer_id" name="Gender">
203
+ <Hierarchy allMemberName="All Genders" hasAll="true" primaryKey="customer_id">
204
+ <Table name="customer"/>
205
+ <Level column="gender" name="Gender" uniqueMembers="true"/>
206
+ </Hierarchy>
207
+ </Dimension>
208
+ </Cube>
209
+ </Schema>
210
+ XML
211
+ end
212
+
213
+ it "should render time dimension" do
214
+ @schema.define do
215
+ cube 'Sales' do
216
+ dimension 'Time' do
217
+ foreign_key 'time_id'
218
+ hierarchy do
219
+ has_all false
220
+ primary_key 'time_id'
221
+ table 'time_by_day'
222
+ level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true
223
+ level 'Quarter', :column => 'quarter', :unique_members => false
224
+ level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false
225
+ end
226
+ end
227
+ end
228
+ end
229
+ @schema.to_xml.should be_like <<-XML
230
+ <?xml version="1.0"?>
231
+ <Schema name="default">
232
+ <Cube name="Sales">
233
+ <Dimension foreignKey="time_id" name="Time">
234
+ <Hierarchy hasAll="false" primaryKey="time_id">
235
+ <Table name="time_by_day"/>
236
+ <Level column="the_year" name="Year" type="Numeric" uniqueMembers="true"/>
237
+ <Level column="quarter" name="Quarter" uniqueMembers="false"/>
238
+ <Level column="month_of_year" name="Month" type="Numeric" uniqueMembers="false"/>
239
+ </Hierarchy>
240
+ </Dimension>
241
+ </Cube>
242
+ </Schema>
243
+ XML
244
+ end
245
+
246
+ it "should render dimension hierarchy with join" do
247
+ @schema.define do
248
+ cube 'Sales' do
249
+ dimension 'Products', :foreign_key => 'product_id' do
250
+ hierarchy :has_all => true, :all_member_name => 'All Products',
251
+ :primary_key => 'product_id', :primary_key_table => 'product' do
252
+ join :left_key => 'product_class_id', :right_key => 'product_class_id' do
253
+ table 'product'
254
+ table 'product_class'
255
+ end
256
+ level 'Product Family', :table => 'product_class', :column => 'product_family', :unique_members => true
257
+ level 'Brand Name', :table => 'product', :column => 'brand_name', :unique_members => false
258
+ level 'Product Name', :table => 'product', :column => 'product_name', :unique_members => true
259
+ end
260
+ end
261
+ end
262
+ end
263
+ @schema.to_xml.should be_like <<-XML
264
+ <?xml version="1.0"?>
265
+ <Schema name="default">
266
+ <Cube name="Sales">
267
+ <Dimension foreignKey="product_id" name="Products">
268
+ <Hierarchy allMemberName="All Products" hasAll="true" primaryKey="product_id" primaryKeyTable="product">
269
+ <Join leftKey="product_class_id" rightKey="product_class_id">
270
+ <Table name="product"/>
271
+ <Table name="product_class"/>
272
+ </Join>
273
+ <Level column="product_family" name="Product Family" table="product_class" uniqueMembers="true"/>
274
+ <Level column="brand_name" name="Brand Name" table="product" uniqueMembers="false"/>
275
+ <Level column="product_name" name="Product Name" table="product" uniqueMembers="true"/>
276
+ </Hierarchy>
277
+ </Dimension>
278
+ </Cube>
279
+ </Schema>
280
+ XML
281
+ end
282
+
283
+ it "should render table and column names in uppercase when using Oracle driver" do
284
+ @schema.define do
285
+ cube 'Sales' do
286
+ dimension 'Products', :foreign_key => 'product_id' do
287
+ hierarchy :has_all => true, :all_member_name => 'All Products',
288
+ :primary_key => 'product_id', :primary_key_table => 'product' do
289
+ join :left_key => 'product_class_id', :right_key => 'product_class_id' do
290
+ table 'product'
291
+ table 'product_class'
292
+ end
293
+ level 'Product Family', :table => 'product_class', :column => 'product_family', :unique_members => true
294
+ level 'Brand Name', :table => 'product', :column => 'brand_name', :unique_members => false
295
+ level 'Product Name', :table => 'product', :column => 'product_name', :unique_members => true
296
+ end
297
+ end
298
+ end
299
+ end
300
+ @schema.to_xml(:driver => 'oracle').should be_like <<-XML
301
+ <?xml version="1.0"?>
302
+ <Schema name="default">
303
+ <Cube name="Sales">
304
+ <Dimension foreignKey="PRODUCT_ID" name="Products">
305
+ <Hierarchy allMemberName="All Products" hasAll="true" primaryKey="PRODUCT_ID" primaryKeyTable="PRODUCT">
306
+ <Join leftKey="PRODUCT_CLASS_ID" rightKey="PRODUCT_CLASS_ID">
307
+ <Table name="PRODUCT"/>
308
+ <Table name="PRODUCT_CLASS"/>
309
+ </Join>
310
+ <Level column="PRODUCT_FAMILY" name="Product Family" table="PRODUCT_CLASS" uniqueMembers="true"/>
311
+ <Level column="BRAND_NAME" name="Brand Name" table="PRODUCT" uniqueMembers="false"/>
312
+ <Level column="PRODUCT_NAME" name="Product Name" table="PRODUCT" uniqueMembers="true"/>
313
+ </Hierarchy>
314
+ </Dimension>
315
+ </Cube>
316
+ </Schema>
317
+ XML
318
+ end
319
+
320
+ it "should render dimension hierarchy with nested joins" do
321
+ @schema.define do
322
+ cube 'Sales' do
323
+ dimension 'Products', :foreign_key => 'product_id' do
324
+ hierarchy :has_all => true, :all_member_name => 'All Products',
325
+ :primary_key => 'product_id', :primary_key_table => 'product' do
326
+ join :left_key => 'product_class_id', :right_alias => 'product_class', :right_key => 'product_class_id' do
327
+ table 'product'
328
+ join :left_key => 'product_type_id', :right_key => 'product_type_id' do
329
+ table 'product_class'
330
+ table 'product_type'
331
+ end
332
+ end
333
+ level 'Product Family', :table => 'product_type', :column => 'product_family', :unique_members => true
334
+ level 'Product Category', :table => 'product_class', :column => 'product_category', :unique_members => false
335
+ level 'Brand Name', :table => 'product', :column => 'brand_name', :unique_members => false
336
+ level 'Product Name', :table => 'product', :column => 'product_name', :unique_members => true
337
+ end
338
+ end
339
+ end
340
+ end
341
+ @schema.to_xml.should be_like <<-XML
342
+ <?xml version="1.0"?>
343
+ <Schema name="default">
344
+ <Cube name="Sales">
345
+ <Dimension foreignKey="product_id" name="Products">
346
+ <Hierarchy allMemberName="All Products" hasAll="true" primaryKey="product_id" primaryKeyTable="product">
347
+ <Join leftKey="product_class_id" rightAlias="product_class" rightKey="product_class_id">
348
+ <Table name="product"/>
349
+ <Join leftKey="product_type_id" rightKey="product_type_id">
350
+ <Table name="product_class"/>
351
+ <Table name="product_type"/>
352
+ </Join>
353
+ </Join>
354
+ <Level column="product_family" name="Product Family" table="product_type" uniqueMembers="true"/>
355
+ <Level column="product_category" name="Product Category" table="product_class" uniqueMembers="false"/>
356
+ <Level column="brand_name" name="Brand Name" table="product" uniqueMembers="false"/>
357
+ <Level column="product_name" name="Product Name" table="product" uniqueMembers="true"/>
358
+ </Hierarchy>
359
+ </Dimension>
360
+ </Cube>
361
+ </Schema>
362
+ XML
363
+ end
364
+
365
+ end
366
+
367
+ describe "Measure" do
368
+ it "should render XML" do
369
+ @schema.define do
370
+ cube 'Sales' do
371
+ measure 'Unit Sales' do
372
+ column 'unit_sales'
373
+ aggregator 'sum'
374
+ end
375
+ end
376
+ end
377
+ @schema.to_xml.should be_like <<-XML
378
+ <?xml version="1.0"?>
379
+ <Schema name="default">
380
+ <Cube name="Sales">
381
+ <Measure aggregator="sum" column="unit_sales" name="Unit Sales"/>
382
+ </Cube>
383
+ </Schema>
384
+ XML
385
+ end
386
+
387
+ it "should render column name in uppercase when using Oracle driver" do
388
+ @schema.define do
389
+ cube 'Sales' do
390
+ measure 'Unit Sales' do
391
+ column 'unit_sales'
392
+ aggregator 'sum'
393
+ end
394
+ end
395
+ end
396
+ @schema.to_xml(:driver => 'oracle').should be_like <<-XML
397
+ <?xml version="1.0"?>
398
+ <Schema name="default">
399
+ <Cube name="Sales">
400
+ <Measure aggregator="sum" column="UNIT_SALES" name="Unit Sales"/>
401
+ </Cube>
402
+ </Schema>
403
+ XML
404
+ end
405
+
406
+ it "should render with measure expression" do
407
+ @schema.define do
408
+ cube 'Sales' do
409
+ measure 'Double Unit Sales', :aggregator => 'sum' do
410
+ measure_expression do
411
+ sql 'unit_sales * 2'
412
+ end
413
+ end
414
+ end
415
+ end
416
+ @schema.to_xml.should be_like <<-XML
417
+ <?xml version="1.0"?>
418
+ <Schema name="default">
419
+ <Cube name="Sales">
420
+ <Measure aggregator="sum" name="Double Unit Sales">
421
+ <MeasureExpression>
422
+ <SQL>unit_sales * 2</SQL>
423
+ </MeasureExpression>
424
+ </Measure>
425
+ </Cube>
426
+ </Schema>
427
+ XML
428
+ end
429
+ end
430
+
431
+ describe "Calculated Member" do
432
+ it "should render XML" do
433
+ @schema.define do
434
+ cube 'Sales' do
435
+ calculated_member 'Profit' do
436
+ dimension 'Measures'
437
+ formula '[Measures].[Store Sales] - [Measures].[Store Cost]'
438
+ format_string '#,##0.00'
439
+ end
440
+ end
441
+ end
442
+ @schema.to_xml.should be_like <<-XML
443
+ <?xml version="1.0"?>
444
+ <Schema name="default">
445
+ <Cube name="Sales">
446
+ <CalculatedMember dimension="Measures" formatString="#,##0.00" name="Profit">
447
+ <Formula>[Measures].[Store Sales] - [Measures].[Store Cost]</Formula>
448
+ </CalculatedMember>
449
+ </Cube>
450
+ </Schema>
451
+ XML
452
+ end
453
+ end
454
+
455
+ describe "Aggregates" do
456
+ it "should render named aggregate to XML" do
457
+ @schema.define do
458
+ cube 'Sales' do
459
+ table 'sales_fact_1997' do
460
+ agg_name 'agg_c_special_sales_fact_1997' do
461
+ agg_fact_count :column => 'fact_count'
462
+ agg_measure '[Measures].[Store Cost]', :column => 'store_cost_sum'
463
+ agg_measure '[Measures].[Store Sales]', :column => 'store_sales_sum'
464
+ agg_level '[Product].[Product Family]', :column => 'product_family'
465
+ agg_level '[Time].[Quarter]', :column => 'time_quarter'
466
+ agg_level '[Time].[Year]', :column => 'time_year'
467
+ agg_level '[Time].[Quarter]', :column => 'time_quarter'
468
+ agg_level '[Time].[Month]', :column => 'time_month'
469
+ end
470
+ end
471
+ end
472
+ end
473
+ @schema.to_xml.should be_like <<-XML
474
+ <?xml version="1.0"?>
475
+ <Schema name="default">
476
+ <Cube name="Sales">
477
+ <Table name="sales_fact_1997">
478
+ <AggName name="agg_c_special_sales_fact_1997">
479
+ <AggFactCount column="fact_count"/>
480
+ <AggMeasure column="store_cost_sum" name="[Measures].[Store Cost]"/>
481
+ <AggMeasure column="store_sales_sum" name="[Measures].[Store Sales]"/>
482
+ <AggLevel column="product_family" name="[Product].[Product Family]"/>
483
+ <AggLevel column="time_quarter" name="[Time].[Quarter]"/>
484
+ <AggLevel column="time_year" name="[Time].[Year]"/>
485
+ <AggLevel column="time_quarter" name="[Time].[Quarter]"/>
486
+ <AggLevel column="time_month" name="[Time].[Month]"/>
487
+ </AggName>
488
+ </Table>
489
+ </Cube>
490
+ </Schema>
491
+ XML
492
+ end
493
+
494
+ it "should render aggregate pattern to XML" do
495
+ @schema.define do
496
+ cube 'Sales' do
497
+ table 'sales_fact_1997' do
498
+ agg_pattern :pattern => 'agg_.*_sales_fact_1997' do
499
+ agg_fact_count :column => 'fact_count'
500
+ agg_measure '[Measures].[Store Cost]', :column => 'store_cost_sum'
501
+ agg_measure '[Measures].[Store Sales]', :column => 'store_sales_sum'
502
+ agg_level '[Product].[Product Family]', :column => 'product_family'
503
+ agg_level '[Time].[Quarter]', :column => 'time_quarter'
504
+ agg_level '[Time].[Year]', :column => 'time_year'
505
+ agg_level '[Time].[Quarter]', :column => 'time_quarter'
506
+ agg_level '[Time].[Month]', :column => 'time_month'
507
+ agg_exclude 'agg_c_14_sales_fact_1997'
508
+ agg_exclude 'agg_lc_100_sales_fact_1997'
509
+ end
510
+ end
511
+ end
512
+ end
513
+ @schema.to_xml.should be_like <<-XML
514
+ <?xml version="1.0"?>
515
+ <Schema name="default">
516
+ <Cube name="Sales">
517
+ <Table name="sales_fact_1997">
518
+ <AggPattern pattern="agg_.*_sales_fact_1997">
519
+ <AggFactCount column="fact_count"/>
520
+ <AggMeasure column="store_cost_sum" name="[Measures].[Store Cost]"/>
521
+ <AggMeasure column="store_sales_sum" name="[Measures].[Store Sales]"/>
522
+ <AggLevel column="product_family" name="[Product].[Product Family]"/>
523
+ <AggLevel column="time_quarter" name="[Time].[Quarter]"/>
524
+ <AggLevel column="time_year" name="[Time].[Year]"/>
525
+ <AggLevel column="time_quarter" name="[Time].[Quarter]"/>
526
+ <AggLevel column="time_month" name="[Time].[Month]"/>
527
+ <AggExclude name="agg_c_14_sales_fact_1997"/>
528
+ <AggExclude name="agg_lc_100_sales_fact_1997"/>
529
+ </AggPattern>
530
+ </Table>
531
+ </Cube>
532
+ </Schema>
533
+ XML
534
+ end
535
+
536
+ it "should render embedded aggregate XML defintion to XML" do
537
+ @schema.define do
538
+ cube 'Sales' do
539
+ table 'sales_fact_1997' do
540
+ xml <<-XML
541
+ <AggName name="agg_c_special_sales_fact_1997">
542
+ <AggFactCount column="fact_count"/>
543
+ <AggMeasure column="store_cost_sum" name="[Measures].[Store Cost]"/>
544
+ <AggMeasure column="store_sales_sum" name="[Measures].[Store Sales]"/>
545
+ <AggLevel column="product_family" name="[Product].[Product Family]"/>
546
+ <AggLevel column="time_quarter" name="[Time].[Quarter]"/>
547
+ <AggLevel column="time_year" name="[Time].[Year]"/>
548
+ <AggLevel column="time_quarter" name="[Time].[Quarter]"/>
549
+ <AggLevel column="time_month" name="[Time].[Month]"/>
550
+ </AggName>
551
+ <AggPattern pattern="agg_.*_sales_fact_1997">
552
+ <AggFactCount column="fact_count"/>
553
+ <AggMeasure column="store_cost_sum" name="[Measures].[Store Cost]"/>
554
+ <AggMeasure column="store_sales_sum" name="[Measures].[Store Sales]"/>
555
+ <AggLevel column="product_family" name="[Product].[Product Family]"/>
556
+ <AggLevel column="time_quarter" name="[Time].[Quarter]"/>
557
+ <AggLevel column="time_year" name="[Time].[Year]"/>
558
+ <AggLevel column="time_quarter" name="[Time].[Quarter]"/>
559
+ <AggLevel column="time_month" name="[Time].[Month]"/>
560
+ <AggExclude name="agg_c_14_sales_fact_1997"/>
561
+ <AggExclude name="agg_lc_100_sales_fact_1997"/>
562
+ </AggPattern>
563
+ XML
564
+ end
565
+ end
566
+ end
567
+ @schema.to_xml.should be_like <<-XML
568
+ <?xml version="1.0"?>
569
+ <Schema name="default">
570
+ <Cube name="Sales">
571
+ <Table name="sales_fact_1997">
572
+ <AggName name="agg_c_special_sales_fact_1997">
573
+ <AggFactCount column="fact_count"/>
574
+ <AggMeasure column="store_cost_sum" name="[Measures].[Store Cost]"/>
575
+ <AggMeasure column="store_sales_sum" name="[Measures].[Store Sales]"/>
576
+ <AggLevel column="product_family" name="[Product].[Product Family]"/>
577
+ <AggLevel column="time_quarter" name="[Time].[Quarter]"/>
578
+ <AggLevel column="time_year" name="[Time].[Year]"/>
579
+ <AggLevel column="time_quarter" name="[Time].[Quarter]"/>
580
+ <AggLevel column="time_month" name="[Time].[Month]"/>
581
+ </AggName>
582
+ <AggPattern pattern="agg_.*_sales_fact_1997">
583
+ <AggFactCount column="fact_count"/>
584
+ <AggMeasure column="store_cost_sum" name="[Measures].[Store Cost]"/>
585
+ <AggMeasure column="store_sales_sum" name="[Measures].[Store Sales]"/>
586
+ <AggLevel column="product_family" name="[Product].[Product Family]"/>
587
+ <AggLevel column="time_quarter" name="[Time].[Quarter]"/>
588
+ <AggLevel column="time_year" name="[Time].[Year]"/>
589
+ <AggLevel column="time_quarter" name="[Time].[Quarter]"/>
590
+ <AggLevel column="time_month" name="[Time].[Month]"/>
591
+ <AggExclude name="agg_c_14_sales_fact_1997"/>
592
+ <AggExclude name="agg_lc_100_sales_fact_1997"/>
593
+ </AggPattern>
594
+ </Table>
595
+ </Cube>
596
+ </Schema>
597
+ XML
598
+ end
599
+
600
+ end
601
+
602
+ describe "Member properties" do
603
+ it "should render XML" do
604
+ @schema.define do
605
+ cube 'Sales' do
606
+ dimension 'Employees', :foreign_key => 'employee_id' do
607
+ hierarchy :has_all => true, :all_member_name => 'All Employees', :primary_key => 'employee_id' do
608
+ table 'employee'
609
+ level 'Employee Id', :unique_members => true, :type => 'Numeric', :column => 'employee_id', :name_column => 'full_name',
610
+ :parent_column => 'supervisor_id', :null_parent_value => 0 do
611
+ property 'Marital Status', :column => 'marital_status'
612
+ property 'Position Title', :column => 'position_title'
613
+ property 'Gender', :column => 'gender'
614
+ property 'Salary', :column => 'salary'
615
+ property 'Education Level', :column => 'education_level'
616
+ end
617
+ end
618
+ end
619
+ end
620
+ end
621
+ @schema.to_xml.should be_like <<-XML
622
+ <?xml version="1.0"?>
623
+ <Schema name="default">
624
+ <Cube name="Sales">
625
+ <Dimension foreignKey="employee_id" name="Employees">
626
+ <Hierarchy allMemberName="All Employees" hasAll="true" primaryKey="employee_id">
627
+ <Table name="employee"/>
628
+ <Level column="employee_id" name="Employee Id" nameColumn="full_name" nullParentValue="0" parentColumn="supervisor_id" type="Numeric" uniqueMembers="true">
629
+ <Property column="marital_status" name="Marital Status"/>
630
+ <Property column="position_title" name="Position Title"/>
631
+ <Property column="gender" name="Gender"/>
632
+ <Property column="salary" name="Salary"/>
633
+ <Property column="education_level" name="Education Level"/>
634
+ </Level>
635
+ </Hierarchy>
636
+ </Dimension>
637
+ </Cube>
638
+ </Schema>
639
+ XML
640
+ end
641
+ end
642
+
643
+ describe "User defined functions and formatters in JavaScript" do
644
+ before(:each) do
645
+ @schema.define do
646
+ cube 'Sales' do
647
+ table 'sales'
648
+ dimension 'Customers', :foreign_key => 'customer_id' do
649
+ hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
650
+ table 'customers'
651
+ level 'Name', :column => 'fullname' do
652
+ member_formatter { javascript "return member.getName().toUpperCase();" }
653
+ property 'City', :column => 'city' do
654
+ property_formatter { javascript "return propertyValue.toUpperCase();" }
655
+ end
656
+ end
657
+ end
658
+ end
659
+ calculated_member 'Factorial' do
660
+ dimension 'Measures'
661
+ formula 'Factorial(6)'
662
+ cell_formatter do
663
+ javascript <<-JS
664
+ var s = value.toString();
665
+ while (s.length < 20) {
666
+ s = "0" + s;
667
+ }
668
+ return s;
669
+ JS
670
+ end
671
+ end
672
+ calculated_member 'City' do
673
+ dimension 'Measures'
674
+ formula "[Customers].CurrentMember.Properties('City')"
675
+ end
676
+ end
677
+ user_defined_function 'Factorial' do
678
+ javascript <<-JS
679
+ function getParameterTypes() {
680
+ return new Array(
681
+ new mondrian.olap.type.NumericType());
682
+ }
683
+ function getReturnType(parameterTypes) {
684
+ return new mondrian.olap.type.NumericType();
685
+ }
686
+ function execute(evaluator, arguments) {
687
+ var n = arguments[0].evaluateScalar(evaluator);
688
+ return factorial(n);
689
+ }
690
+ function factorial(n) {
691
+ return n <= 1 ? 1 : n * factorial(n - 1);
692
+ }
693
+ JS
694
+ end
695
+ end
696
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
697
+ end
698
+
699
+ it "should render XML" do
700
+ @schema.to_xml.should be_like <<-XML
701
+ <?xml version="1.0"?>
702
+ <Schema name="default">
703
+ <Cube name="Sales">
704
+ <Table name="sales"/>
705
+ <Dimension foreignKey="customer_id" name="Customers">
706
+ <Hierarchy allMemberName="All Customers" hasAll="true" primaryKey="id">
707
+ <Table name="customers"/>
708
+ <Level column="fullname" name="Name">
709
+ <MemberFormatter>
710
+ <Script language="JavaScript">return member.getName().toUpperCase();</Script>
711
+ </MemberFormatter>
712
+ <Property column="city" name="City">
713
+ <PropertyFormatter>
714
+ <Script language="JavaScript">return propertyValue.toUpperCase();</Script>
715
+ </PropertyFormatter>
716
+ </Property>
717
+ </Level>
718
+ </Hierarchy>
719
+ </Dimension>
720
+ <CalculatedMember dimension="Measures" name="Factorial">
721
+ <Formula>Factorial(6)</Formula>
722
+ <CellFormatter>
723
+ <Script language="JavaScript">
724
+ var s = value.toString();
725
+ while (s.length &lt; 20) {
726
+ s = "0" + s;
727
+ }
728
+ return s;
729
+ </Script>
730
+ </CellFormatter>
731
+ </CalculatedMember>
732
+ <CalculatedMember dimension="Measures" name="City">
733
+ <Formula>[Customers].CurrentMember.Properties('City')</Formula>
734
+ </CalculatedMember>
735
+ </Cube>
736
+ <UserDefinedFunction name="Factorial">
737
+ <Script language="JavaScript">
738
+ function getParameterTypes() {
739
+ return new Array(
740
+ new mondrian.olap.type.NumericType());
741
+ }
742
+ function getReturnType(parameterTypes) {
743
+ return new mondrian.olap.type.NumericType();
744
+ }
745
+ function execute(evaluator, arguments) {
746
+ var n = arguments[0].evaluateScalar(evaluator);
747
+ return factorial(n);
748
+ }
749
+ function factorial(n) {
750
+ return n &lt;= 1 ? 1 : n * factorial(n - 1);
751
+ }
752
+ </Script>
753
+ </UserDefinedFunction>
754
+ </Schema>
755
+ XML
756
+ end
757
+
758
+ it "should execute user defined function" do
759
+ result = @olap.from('Sales').columns('[Measures].[Factorial]').execute
760
+ value = 1*2*3*4*5*6
761
+ result.values.should == [value]
762
+ result.formatted_values.should == ["%020d" % value]
763
+ end
764
+
765
+ it "should format members and properties" do
766
+ result = @olap.from('Sales').columns('[Measures].[City]').rows('[Customers].[All Customers].Children').execute
767
+ result.row_members.each_with_index do |member, i|
768
+ member.caption.should == member.name.upcase
769
+ city = member.property_value('City')
770
+ result.formatted_values[i].first.should == city
771
+ member.property_formatted_value('City').should == city.upcase
772
+ end
773
+ end
774
+ end
775
+
776
+ describe "User defined functions and formatters in CoffeeScript" do
777
+ before(:each) do
778
+ @schema.define do
779
+ cube 'Sales' do
780
+ table 'sales'
781
+ dimension 'Customers', :foreign_key => 'customer_id' do
782
+ hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
783
+ table 'customers'
784
+ level 'Name', :column => 'fullname' do
785
+ member_formatter { coffeescript "member.getName().toUpperCase()" }
786
+ property 'City', :column => 'city' do
787
+ property_formatter { coffeescript "propertyValue.toUpperCase()" }
788
+ end
789
+ end
790
+ end
791
+ end
792
+ calculated_member 'Factorial' do
793
+ dimension 'Measures'
794
+ formula 'Factorial(6)'
795
+ cell_formatter do
796
+ coffeescript <<-JS
797
+ s = value.toString()
798
+ s = "0" + s while s.length < 20
799
+ s
800
+ JS
801
+ end
802
+ end
803
+ calculated_member 'City' do
804
+ dimension 'Measures'
805
+ formula "[Customers].CurrentMember.Properties('City')"
806
+ end
807
+ end
808
+ user_defined_function 'Factorial' do
809
+ coffeescript <<-JS
810
+ parameters: ["Numeric"]
811
+ returns: "Numeric"
812
+ execute: (n) ->
813
+ if n <= 1 then 1 else n * @execute(n - 1)
814
+ JS
815
+ end
816
+ user_defined_function 'UpperName' do
817
+ coffeescript <<-JS
818
+ parameters: ["Member"]
819
+ returns: "String"
820
+ syntax: "Property"
821
+ execute: (member) ->
822
+ member.getName().toUpperCase()
823
+ JS
824
+ end
825
+ user_defined_function 'toUpperName' do
826
+ coffeescript <<-JS
827
+ parameters: ["Member", "String"]
828
+ returns: "String"
829
+ syntax: "Method"
830
+ execute: (member, dummy) ->
831
+ member.getName().toUpperCase()
832
+ JS
833
+ end
834
+ end
835
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
836
+ end
837
+
838
+ it "should execute user defined function" do
839
+ result = @olap.from('Sales').columns('[Measures].[Factorial]').execute
840
+ value = 1*2*3*4*5*6
841
+ result.values.should == [value]
842
+ result.formatted_values.should == ["%020d" % value]
843
+ end
844
+
845
+ it "should format members and properties" do
846
+ result = @olap.from('Sales').columns('[Measures].[City]').rows('[Customers].[All Customers].Children').execute
847
+ result.row_members.each_with_index do |member, i|
848
+ member.caption.should == member.name.upcase
849
+ city = member.property_value('City')
850
+ result.formatted_values[i].first.should == city
851
+ member.property_formatted_value('City').should == city.upcase
852
+ end
853
+ end
854
+
855
+ it "should execute user defined property on member" do
856
+ result = @olap.from('Sales').
857
+ with_member('[Measures].[Upper Name]').as('[Customers].CurrentMember.UpperName').
858
+ columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
859
+ result.row_members.each_with_index do |member, i|
860
+ result.values[i].should == [member.name.upcase]
861
+ end
862
+ end
863
+
864
+ it "should execute user defined method on member" do
865
+ result = @olap.from('Sales').
866
+ with_member('[Measures].[Upper Name]').as("[Customers].CurrentMember.toUpperName('dummy')").
867
+ columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
868
+ result.row_members.each_with_index do |member, i|
869
+ result.values[i].should == [member.name.upcase]
870
+ end
871
+ end
872
+ end
873
+
874
+ describe "User defined functions and formatters in Ruby" do
875
+ before(:each) do
876
+ @schema.define do
877
+ cube 'Sales' do
878
+ table 'sales'
879
+ dimension 'Customers', :foreign_key => 'customer_id' do
880
+ hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
881
+ table 'customers'
882
+ level 'Name', :column => 'fullname' do
883
+ member_formatter { ruby {|member| member.getName().upcase } }
884
+ property 'City', :column => 'city' do
885
+ property_formatter { ruby {|member, property_name, property_value| property_value.upcase} }
886
+ end
887
+ end
888
+ end
889
+ end
890
+ calculated_member 'Factorial' do
891
+ dimension 'Measures'
892
+ formula 'Factorial(6)'
893
+ cell_formatter { ruby {|value| "%020d" % value} }
894
+ end
895
+ calculated_member 'City' do
896
+ dimension 'Measures'
897
+ formula "[Customers].CurrentMember.Properties('City')"
898
+ end
899
+ end
900
+ user_defined_function 'Factorial' do
901
+ ruby do
902
+ parameters :numeric
903
+ returns :numeric
904
+ def call(n)
905
+ n <= 1 ? 1 : n * call(n - 1)
906
+ end
907
+ end
908
+ end
909
+ user_defined_function 'UpperName' do
910
+ ruby do
911
+ parameters :member
912
+ returns :string
913
+ syntax :property
914
+ def call(member)
915
+ member.getName.upcase
916
+ end
917
+ end
918
+ end
919
+ user_defined_function 'toUpperName' do
920
+ ruby do
921
+ parameters :member, :string
922
+ returns :string
923
+ syntax :method
924
+ def call(member, dummy)
925
+ member.getName.upcase
926
+ end
927
+ end
928
+ end
929
+ user_defined_function 'firstUpperName' do
930
+ ruby do
931
+ parameters :set
932
+ returns :string
933
+ syntax :property
934
+ def call(set)
935
+ set.first.getName.upcase
936
+ end
937
+ end
938
+ end
939
+ user_defined_function 'firstToUpperName' do
940
+ ruby do
941
+ parameters :set, :string
942
+ returns :string
943
+ syntax :method
944
+ def call(set, dummy)
945
+ set.first.getName.upcase
946
+ end
947
+ end
948
+ end
949
+ user_defined_function 'firstChildUpperName' do
950
+ ruby do
951
+ parameters :hierarchy
952
+ returns :string
953
+ syntax :property
954
+ def call_with_evaluator(evaluator, hierarchy)
955
+ evaluator.getSchemaReader.getMemberChildren(hierarchy.getDefaultMember).first.getName.upcase
956
+ end
957
+ end
958
+ end
959
+ user_defined_function 'firstLevelChildUpperName' do
960
+ ruby do
961
+ parameters :level
962
+ returns :string
963
+ syntax :property
964
+ def call_with_evaluator(evaluator, level)
965
+ evaluator.getSchemaReader.getLevelMembers(level, false).first.getName.upcase
966
+ end
967
+ end
968
+ end
969
+ end
970
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
971
+ end
972
+
973
+ it "should execute user defined function" do
974
+ result = @olap.from('Sales').columns('[Measures].[Factorial]').execute
975
+ value = 1*2*3*4*5*6
976
+ result.values.should == [value]
977
+ result.formatted_values.should == ["%020d" % value]
978
+ end
979
+
980
+ it "should format members and properties" do
981
+ result = @olap.from('Sales').columns('[Measures].[City]').rows('[Customers].[All Customers].Children').execute
982
+ result.row_members.each_with_index do |member, i|
983
+ member.caption.should == member.name.upcase
984
+ city = member.property_value('City')
985
+ result.formatted_values[i].first.should == city
986
+ member.property_formatted_value('City').should == city.upcase
987
+ end
988
+ end
989
+
990
+ it "should execute user defined property on member" do
991
+ result = @olap.from('Sales').
992
+ with_member('[Measures].[Upper Name]').as('[Customers].CurrentMember.UpperName').
993
+ columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
994
+ result.row_members.each_with_index do |member, i|
995
+ result.values[i].should == [member.name.upcase]
996
+ end
997
+ end
998
+
999
+ it "should execute user defined method on member" do
1000
+ result = @olap.from('Sales').
1001
+ with_member('[Measures].[Upper Name]').as("[Customers].CurrentMember.toUpperName('dummy')").
1002
+ columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
1003
+ result.row_members.each_with_index do |member, i|
1004
+ result.values[i].should == [member.name.upcase]
1005
+ end
1006
+ end
1007
+
1008
+ it "should execute user defined property on set" do
1009
+ result = @olap.from('Sales').
1010
+ with_member('[Measures].[Upper Name]').as("{[Customers].CurrentMember}.firstUpperName").
1011
+ columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
1012
+ result.row_members.each_with_index do |member, i|
1013
+ result.values[i].should == [member.name.upcase]
1014
+ end
1015
+ end
1016
+
1017
+ it "should execute user defined method on set" do
1018
+ result = @olap.from('Sales').
1019
+ with_member('[Measures].[Upper Name]').as("{[Customers].CurrentMember}.firstToUpperName('dummy')").
1020
+ columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
1021
+ result.row_members.each_with_index do |member, i|
1022
+ result.values[i].should == [member.name.upcase]
1023
+ end
1024
+ end
1025
+
1026
+ it "should execute user defined property on hierarchy" do
1027
+ result = @olap.from('Sales').
1028
+ with_member('[Measures].[Upper Name]').as("[Customers].firstChildUpperName").
1029
+ columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
1030
+ first_member = result.row_members.first
1031
+ result.row_members.each_with_index do |member, i|
1032
+ result.values[i].should == [first_member.name.upcase]
1033
+ end
1034
+ end
1035
+
1036
+ it "should execute user defined property on level" do
1037
+ result = @olap.from('Sales').
1038
+ with_member('[Measures].[Upper Name]').as("[Customers].[Name].firstLevelChildUpperName").
1039
+ columns('[Measures].[Upper Name]').rows('[Customers].Children').execute
1040
+ first_member = result.row_members.first
1041
+ result.row_members.each_with_index do |member, i|
1042
+ result.values[i].should == [first_member.name.upcase]
1043
+ end
1044
+ end
1045
+ end
1046
+
1047
+ describe "Shared user defined functions in Ruby" do
1048
+ before(:each) do
1049
+ shared_schema = Mondrian::OLAP::Schema.define do
1050
+ user_defined_function 'Factorial' do
1051
+ ruby :shared do
1052
+ parameters :numeric
1053
+ returns :numeric
1054
+ def call(n)
1055
+ n <= 1 ? 1 : n * call(n - 1)
1056
+ end
1057
+ end
1058
+ end
1059
+ user_defined_function 'UpperName' do
1060
+ ruby :shared do
1061
+ parameters :member
1062
+ returns :string
1063
+ syntax :property
1064
+ def call(member)
1065
+ member.getName.upcase
1066
+ end
1067
+ end
1068
+ end
1069
+ user_defined_function 'toUpperName' do
1070
+ ruby :shared do
1071
+ parameters :member, :string
1072
+ returns :string
1073
+ syntax :method
1074
+ def call(member, dummy)
1075
+ member.getName.upcase
1076
+ end
1077
+ end
1078
+ end
1079
+ end
1080
+
1081
+ @schema.define do
1082
+ include_schema shared_schema
1083
+
1084
+ cube 'Sales' do
1085
+ table 'sales'
1086
+ dimension 'Customers', :foreign_key => 'customer_id' do
1087
+ hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
1088
+ table 'customers'
1089
+ level 'Name', :column => 'fullname'
1090
+ end
1091
+ end
1092
+ calculated_member 'Factorial' do
1093
+ dimension 'Measures'
1094
+ formula 'Factorial(6)'
1095
+ end
1096
+ end
1097
+ end
1098
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
1099
+ end
1100
+
1101
+ it "should render XML" do
1102
+ @schema.to_xml.should be_like <<-XML
1103
+ <?xml version="1.0"?>
1104
+ <Schema name="default">
1105
+ <Cube name="Sales">
1106
+ <Table name="sales"/>
1107
+ <Dimension foreignKey="customer_id" name="Customers">
1108
+ <Hierarchy allMemberName="All Customers" hasAll="true" primaryKey="id">
1109
+ <Table name="customers"/>
1110
+ <Level column="fullname" name="Name"/>
1111
+ </Hierarchy>
1112
+ </Dimension>
1113
+ <CalculatedMember dimension="Measures" name="Factorial">
1114
+ <Formula>Factorial(6)</Formula>
1115
+ </CalculatedMember>
1116
+ </Cube>
1117
+ <UserDefinedFunction className="rubyobj.Mondrian.OLAP.Schema.UserDefinedFunction.FactorialUdf" name="Factorial"/>
1118
+ <UserDefinedFunction className="rubyobj.Mondrian.OLAP.Schema.UserDefinedFunction.UppernameUdf" name="UpperName"/>
1119
+ <UserDefinedFunction className="rubyobj.Mondrian.OLAP.Schema.UserDefinedFunction.TouppernameUdf" name="toUpperName"/>
1120
+ </Schema>
1121
+ XML
1122
+ end
1123
+
1124
+ it "should execute user defined function" do
1125
+ result = @olap.from('Sales').columns('[Measures].[Factorial]').execute
1126
+ value = 1*2*3*4*5*6
1127
+ result.values.should == [value]
1128
+ end
1129
+
1130
+ end
1131
+
1132
+ describe "Roles" do
1133
+ it "should render XML" do
1134
+ @schema.define do
1135
+ role 'California manager' do
1136
+ schema_grant :access => 'none' do
1137
+ cube_grant :cube => 'Sales', :access => 'all' do
1138
+ dimension_grant :dimension => '[Measures]', :access => 'all'
1139
+ hierarchy_grant :hierarchy => '[Customers]', :access => 'custom',
1140
+ :top_level => '[Customers].[State Province]', :bottom_level => '[Customers].[City]' do
1141
+ member_grant :member => '[Customers].[USA].[CA]', :access => 'all'
1142
+ member_grant :member => '[Customers].[USA].[CA].[Los Angeles]', :access => 'none'
1143
+ end
1144
+ end
1145
+ end
1146
+ end
1147
+ end
1148
+ @schema.to_xml.should be_like <<-XML
1149
+ <?xml version="1.0"?>
1150
+ <Schema name="default">
1151
+ <Role name="California manager">
1152
+ <SchemaGrant access="none">
1153
+ <CubeGrant access="all" cube="Sales">
1154
+ <DimensionGrant access="all" dimension="[Measures]"/>
1155
+ <HierarchyGrant access="custom" bottomLevel="[Customers].[City]" hierarchy="[Customers]" topLevel="[Customers].[State Province]">
1156
+ <MemberGrant access="all" member="[Customers].[USA].[CA]"/>
1157
+ <MemberGrant access="none" member="[Customers].[USA].[CA].[Los Angeles]"/>
1158
+ </HierarchyGrant>
1159
+ </CubeGrant>
1160
+ </SchemaGrant>
1161
+ </Role>
1162
+ </Schema>
1163
+ XML
1164
+ end
1165
+ end
1166
+
1167
+ end
1168
+
1169
+ describe "connection with schema" do
1170
+ before(:all) do
1171
+ @schema = Mondrian::OLAP::Schema.define do
1172
+ cube 'Sales' do
1173
+ table 'sales'
1174
+ dimension 'Gender', :foreign_key => 'customer_id' do
1175
+ hierarchy :has_all => true, :primary_key => 'id' do
1176
+ table 'customers'
1177
+ level 'Gender', :column => 'gender', :unique_members => true
1178
+ end
1179
+ end
1180
+ dimension 'Time', :foreign_key => 'time_id' do
1181
+ hierarchy :has_all => false, :primary_key => 'id' do
1182
+ table 'time'
1183
+ level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true
1184
+ level 'Quarter', :column => 'quarter', :unique_members => false
1185
+ level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false
1186
+ end
1187
+ end
1188
+ measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum'
1189
+ measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
1190
+ end
1191
+ end
1192
+ @olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
1193
+ end
1194
+
1195
+ it "should connect" do
1196
+ @olap.should be_connected
1197
+ end
1198
+
1199
+ it "should execute query" do
1200
+ @olap.from('Sales').
1201
+ columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
1202
+ rows('descendants([Time].[2010].[Q1])').
1203
+ where('[Gender].[F]').
1204
+ execute.should be_a(Mondrian::OLAP::Result)
1205
+ end
1206
+
1207
+ end
1208
+
1209
+ describe "errors" do
1210
+ before(:each) do
1211
+ @schema = Mondrian::OLAP::Schema.new
1212
+ end
1213
+
1214
+ it "should raise error when invalid schema" do
1215
+ @schema.define do
1216
+ cube 'Sales' do
1217
+ end
1218
+ end
1219
+ expect {
1220
+ Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
1221
+ }.to raise_error {|e|
1222
+ e.should be_kind_of(Mondrian::OLAP::Error)
1223
+ e.message.should == "mondrian.olap.MondrianException: Mondrian Error:Internal error: Must specify fact table of cube 'Sales'"
1224
+ e.root_cause_message.should == "Internal error: Must specify fact table of cube 'Sales'"
1225
+ }
1226
+ end
1227
+
1228
+ it "should raise error when invalid calculated member formula" do
1229
+ @schema.define do
1230
+ cube 'Sales' do
1231
+ table 'sales'
1232
+ calculated_member 'Dummy' do
1233
+ dimension 'Measures'
1234
+ formula 'Dummy(123)'
1235
+ end
1236
+ end
1237
+ end
1238
+ expect {
1239
+ Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
1240
+ }.to raise_error {|e|
1241
+ e.should be_kind_of(Mondrian::OLAP::Error)
1242
+ e.message.should == "mondrian.olap.MondrianException: Mondrian Error:Named set in cube 'Sales' has bad formula"
1243
+ e.root_cause_message.should == "No function matches signature 'Dummy(<Numeric Expression>)'"
1244
+ }
1245
+ end
1246
+
1247
+ end
1248
+
1249
+ end