mondrian-olap 0.4.0-java

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