mondrian-olap 0.3.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/Changelog.md +38 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +302 -0
  5. data/VERSION +1 -1
  6. data/lib/mondrian/jars/commons-collections-3.2.jar +0 -0
  7. data/lib/mondrian/jars/commons-logging-1.1.1.jar +0 -0
  8. data/lib/mondrian/jars/commons-math-1.1.jar +0 -0
  9. data/lib/mondrian/jars/eigenbase-properties-1.1.2.jar +0 -0
  10. data/lib/mondrian/jars/eigenbase-resgen-1.3.1.jar +0 -0
  11. data/lib/mondrian/jars/eigenbase-xom-1.3.1.jar +0 -0
  12. data/lib/mondrian/jars/{javacup.jar → javacup-10k.jar} +0 -0
  13. data/lib/mondrian/jars/log4j-1.2.14.jar +0 -0
  14. data/lib/mondrian/jars/mondrian.jar +0 -0
  15. data/lib/mondrian/jars/olap4j-1.0.1.539.jar +0 -0
  16. data/lib/mondrian/olap.rb +2 -1
  17. data/lib/mondrian/olap/connection.rb +163 -32
  18. data/lib/mondrian/olap/cube.rb +163 -24
  19. data/lib/mondrian/olap/error.rb +57 -0
  20. data/lib/mondrian/olap/query.rb +52 -17
  21. data/lib/mondrian/olap/result.rb +298 -6
  22. data/lib/mondrian/olap/schema.rb +220 -29
  23. data/lib/mondrian/olap/schema_element.rb +31 -11
  24. data/lib/mondrian/olap/schema_udf.rb +331 -0
  25. data/lib/mondrian/olap/version.rb +5 -0
  26. data/spec/connection_role_spec.rb +130 -0
  27. data/spec/connection_spec.rb +36 -1
  28. data/spec/cube_spec.rb +137 -7
  29. data/spec/fixtures/MondrianTest.xml +4 -4
  30. data/spec/mondrian_spec.rb +53 -0
  31. data/spec/query_spec.rb +294 -11
  32. data/spec/rake_tasks.rb +8 -8
  33. data/spec/schema_definition_spec.rb +845 -26
  34. data/spec/spec_helper.rb +26 -17
  35. data/spec/support/matchers/be_like.rb +2 -2
  36. metadata +296 -237
  37. data/.rspec +0 -2
  38. data/Gemfile +0 -18
  39. data/README.rdoc +0 -221
  40. data/RUNNING_TESTS.rdoc +0 -66
  41. data/Rakefile +0 -46
  42. data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
  43. data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
  44. data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
  45. data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
  46. data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
  47. data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
  48. data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
  49. data/lib/mondrian/jars/olap4j.jar +0 -0
  50. data/mondrian-olap.gemspec +0 -126
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a82185c2c1e818166ad00f6a79629772eda89fd6
4
+ data.tar.gz: 48189b91d855f7dc3df6842d91360068285be951
5
+ SHA512:
6
+ metadata.gz: d11c3fb9718d6a81be7f534c1b96a6cf731b64019a947a01bbfb58e3a2d52453bd334c5f083ef05981093657f66b3b4572f64748ae0aa4382f4c6a03c445aa9f
7
+ data.tar.gz: 5f588afd736a0e8c62f05b5a9d56aa75fe181e46373d4025d85522532b8371a3fb439e3d000339ed70ad61d0f8c99ce37e17b9ddc693cfe44a4884ff557c375f
@@ -1,3 +1,41 @@
1
+ ### 0.5.0 / 2013-11-29
2
+
3
+ * New features
4
+ * upgraded to latest Mondrian 3.5 version (build from 2013-07-31)
5
+ * added shutdown_static_mondrian_server! method
6
+ * add schema element annotations in schema definition
7
+ * set connection locale
8
+ * added support for schema elements caption
9
+ * added shared dimension schema definition methods
10
+ * added virtual cube schema definition methods
11
+ * Improvements
12
+ * connection execute_drill_through method
13
+ * define shared user defined cell formatters
14
+ * set default hierarchy :has_all and level :unique_members attributes
15
+ * by default use sum aggregator for measure in schema definition
16
+ * support Oracle connection using slash and service name as database name
17
+ * Bug fixes
18
+ * render cube XML fragment before calculated members in generated XML schema
19
+ * generate XML with UTF-8 encoding
20
+
21
+ ### 0.4.0 / 2012-12-03
22
+
23
+ * New features
24
+ * upgraded to latest Mondrian 3.5 version (build from 2012-11-29)
25
+ as well as corresponding olap4j 1.0.1 version
26
+ * support for JRuby 1.7 and Java 7 VM
27
+ * user defined functions and formatters in JavaScript, CoffeeScript and Ruby
28
+ * shared user defined functions in Ruby
29
+ * all exceptions are wrapped in Mondrian::OLAP::Error exception with root_cause_message method
30
+ * drill through from result cell to source measure and dimension table rows
31
+ * support for Mondrian schema roles to limit cube data access
32
+ * Improvements
33
+ * get description of cube, dimension, hierarchy and level from schema definition
34
+ * visible? method for measures and calculated members
35
+ * nonempty_crossjoin query builder method
36
+ * schema definition with nested table joins
37
+ * added approx_row_count schema level attribute
38
+
1
39
  ### 0.3.0 / 2011-11-12
2
40
 
3
41
  * New features
@@ -1,6 +1,6 @@
1
1
  (The MIT License)
2
2
 
3
- Copyright (c) 2010-2011 Raimonds Simanovskis
3
+ Copyright (c) 2010-2013 Raimonds Simanovskis
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
@@ -0,0 +1,302 @@
1
+ mondrian-olap
2
+ =============
3
+
4
+ JRuby gem for performing multidimensional queries of relational database data using Mondrian OLAP Java library.
5
+
6
+ DESCRIPTION
7
+ -----------
8
+
9
+ SQL language is good for doing ad-hoc queries from relational databases but it becomes very complicated when doing more complex analytical queries to get summary results. Alternative approach is OLAP (On-Line Analytical Processing) databases and engines that provide easier multidimensional analysis of data at different summary levels.
10
+
11
+ One of the most popular open-source OLAP engines is [Mondrian](http://mondrian.pentaho.com). Mondrian OLAP engine can be put in front of relational SQL database and it provides MDX multidimensional query language which is much more suited for analytical purposes.
12
+
13
+ mondrian-olap is JRuby gem which includes Mondrian OLAP engine and provides Ruby DSL for creating OLAP schemas on top of relational database schemas and provides MDX query language and query builder Ruby methods for making analytical queries.
14
+
15
+ mondrian-olap is used in [eazyBI data analysis and reporting web application](https://eazybi.com). [eazyBI remote setup](https://eazybi.com/help/remote-setup) can be used to create easy-to-use web based reports and dashboards on top of mondrian-olap based backend database. There is also [mondrian-olap demo Rails application for trying MDX queries](https://github.com/rsim/mondrian_demo).
16
+
17
+ USAGE
18
+ -----
19
+
20
+ ### Schema definition
21
+
22
+ At first you need to define OLAP schema mapping to relational database schema tables and columns. OLAP schema consists of:
23
+
24
+ * Cubes
25
+
26
+ Multidimensional cube is a collection of measures that can be accessed by dimensions. In relational database cubes are stored in fact tables with measure columns and dimension foreign key columns.
27
+
28
+ * Dimensions
29
+
30
+ Dimension can be used in one cube (private) or in many cubes (shared). In relational database dimensions are stored in dimension tables.
31
+
32
+ * Hierarchies and levels
33
+
34
+ Dimension has at least one primary hierarchy and optional additional hierarchies and each hierarchy has one or more levels. In relational database all levels can be stored in the same dimension table as different columns or can be stored also in several tables.
35
+
36
+ * Members
37
+
38
+ Dimension hierarchy level values are called members.
39
+
40
+ * Measures
41
+
42
+ Measures are values which can be accessed at detailed level or aggregated (e.g. as sum or average) at higher dimension hierarchy levels. In relational database measures are stored as columns in cube table.
43
+
44
+ * Calculated measures
45
+
46
+ Calculated measures are not stored in database but calculated using specified formula from other measures.
47
+
48
+ Read more about about [defining Mondrian OLAP schema](http://mondrian.pentaho.com/documentation/schema.php).
49
+
50
+ Here is example how to define OLAP schema and its mapping to relational database tables and columns using mondrian-olap:
51
+
52
+ ```ruby
53
+ require "rubygems"
54
+ require "mondrian-olap"
55
+
56
+ schema = Mondrian::OLAP::Schema.define do
57
+ cube 'Sales' do
58
+ table 'sales'
59
+ dimension 'Customers', :foreign_key => 'customer_id' do
60
+ hierarchy :has_all => true, :all_member_name => 'All Customers', :primary_key => 'id' do
61
+ table 'customers'
62
+ level 'Country', :column => 'country', :unique_members => true
63
+ level 'State Province', :column => 'state_province', :unique_members => true
64
+ level 'City', :column => 'city', :unique_members => false
65
+ level 'Name', :column => 'fullname', :unique_members => true
66
+ end
67
+ end
68
+ dimension 'Products', :foreign_key => 'product_id' do
69
+ hierarchy :has_all => true, :all_member_name => 'All Products',
70
+ :primary_key => 'id', :primary_key_table => 'products' do
71
+ join :left_key => 'product_class_id', :right_key => 'id' do
72
+ table 'products'
73
+ table 'product_classes'
74
+ end
75
+ level 'Product Family', :table => 'product_classes', :column => 'product_family', :unique_members => true
76
+ level 'Brand Name', :table => 'products', :column => 'brand_name', :unique_members => false
77
+ level 'Product Name', :table => 'products', :column => 'product_name', :unique_members => true
78
+ end
79
+ end
80
+ dimension 'Time', :foreign_key => 'time_id', :type => 'TimeDimension' do
81
+ hierarchy :has_all => false, :primary_key => 'id' do
82
+ table 'time'
83
+ level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
84
+ level 'Quarter', :column => 'quarter', :unique_members => false, :level_type => 'TimeQuarters'
85
+ level 'Month', :column => 'month_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeMonths'
86
+ end
87
+ hierarchy 'Weekly', :has_all => false, :primary_key => 'id' do
88
+ table 'time'
89
+ level 'Year', :column => 'the_year', :type => 'Numeric', :unique_members => true, :level_type => 'TimeYears'
90
+ level 'Week', :column => 'week_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeWeeks'
91
+ end
92
+ end
93
+ measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum'
94
+ measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
95
+ end
96
+ end
97
+ ```
98
+
99
+ ### Connection creation
100
+
101
+ When schema is defined it is necessary to establish OLAP connection to database. Here is example how to connect to MySQL database using the schema object that was defined previously:
102
+
103
+ ```ruby
104
+ require "jdbc/mysql"
105
+
106
+ olap = Mondrian::OLAP::Connection.create(
107
+ :driver => 'mysql',
108
+ :host => 'localhost,
109
+ :database => 'mondrian_test',
110
+ :username => 'mondrian_user',
111
+ :password => 'secret',
112
+ :schema => schema
113
+ )
114
+ ```
115
+
116
+ ### MDX queries
117
+
118
+ Mondrian OLAP provides MDX query language. [Read more about MDX](http://mondrian.pentaho.com/documentation/mdx.php).
119
+ mondrian-olap allows executing of MDX queries, for example query for "Get sales amount and number of units (on columns) of all product families (on rows) sold in California during Q1 of 2010":
120
+
121
+ ```ruby
122
+ result = olap.execute <<-MDX
123
+ SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
124
+ {[Products].children} ON ROWS
125
+ FROM [Sales]
126
+ WHERE ([Time].[2010].[Q1], [Customers].[USA].[CA])
127
+ MDX
128
+ ```
129
+
130
+ which would correspond to the following SQL query:
131
+
132
+ SELECT SUM(unit_sales) unit_sales_sum, SUM(store_sales) store_sales_sum
133
+ FROM sales
134
+ LEFT JOIN products ON sales.product_id = products.id
135
+ LEFT JOIN product_classes ON products.product_class_id = product_classes.id
136
+ LEFT JOIN time ON sales.time_id = time.id
137
+ LEFT JOIN customers ON sales.customer_id = customers.id
138
+ WHERE time.the_year = 2010 AND time.quarter = 'Q1'
139
+ AND customers.country = 'USA' AND customers.state_province = 'CA'
140
+ GROUP BY product_classes.product_family
141
+ ORDER BY product_classes.product_family
142
+
143
+ and then get axis and cells of result object:
144
+
145
+ ```ruby
146
+ result.axes_count # => 2
147
+ result.column_names # => ["Unit Sales", "Store Sales"]
148
+ result.column_full_names # => ["[Measures].[Unit Sales]", "[Measures].[Store Sales]"]
149
+ result.row_names # => e.g. ["Drink", "Food", "Non-Consumable"]
150
+ result.row_full_names # => e.g. ["[Products].[Drink]", "[Products].[Food]", "[Products].[Non-Consumable]"]
151
+ result.values # => [[..., ...], [..., ...], [..., ...]]
152
+ # (three rows, each row containing value for "unit sales" and "store sales")
153
+ ```
154
+
155
+ ### Query builder methods
156
+
157
+ MDX queries could be built and executed also using Ruby methods in a similar way as ActiveRecord/Arel queries are made.
158
+ Previous MDX query can be executed as:
159
+
160
+ ```ruby
161
+ olap.from('Sales').
162
+ columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
163
+ rows('[Products].children').
164
+ where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').
165
+ execute
166
+ ```
167
+
168
+ Here is example of more complex query "Get sales amount and profit % of top 50 products cross-joined with USA and Canada country sales during Q1 of 2010":
169
+
170
+ ```ruby
171
+ olap.from('Sales').
172
+ with_member('[Measures].[ProfitPct]').
173
+ as('Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
174
+ :format_string => 'Percent').
175
+ columns('[Measures].[Store Sales]', '[Measures].[ProfitPct]').
176
+ rows('[Products].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
177
+ top_count(50, '[Measures].[Store Sales]').
178
+ where('[Time].[2010].[Q1]').
179
+ execute
180
+ ```
181
+
182
+ See more examples of queries in `spec/query_spec.rb`.
183
+
184
+ Currently there are query builder methods just for most frequently used MDX functions, there will be new query builder methods in next releases of mondrian-olap gem.
185
+
186
+ ### Cube dimension and member queries
187
+
188
+ mondrian-olap provides also methods for querying dimensions and members:
189
+
190
+ ```ruby
191
+ cube = olap.cube('Sales')
192
+ cube.dimension_names # => ['Measures', 'Customers', 'Products', 'Time']
193
+ cube.dimensions # => array of dimension objects
194
+ cube.dimension('Customers') # => customers dimension object
195
+ cube.dimension('Time').hierarchy_names # => ['Time', 'Time.Weekly']
196
+ cube.dimension('Time').hierarchies # => array of hierarchy objects
197
+ cube.dimension('Customers').hierarchy # => default customers dimension hierarchy
198
+ cube.dimension('Customers').hierarchy.level_names
199
+ # => ['(All)', 'Country', 'State Province', 'City', 'Name']
200
+ cube.dimension('Customers').hierarchy.levels
201
+ # => array of hierarchy level objects
202
+ cube.dimension('Customers').hierarchy.level('Country').members
203
+ # => array of all level members
204
+ cube.member('[Customers].[USA].[CA]') # => lookup member by full name
205
+ cube.member('[Customers].[USA].[CA]').children
206
+ # => get all children of member in deeper hierarchy level
207
+ cube.member('[Customers].[USA]').descendants_at_level('City')
208
+ # => get all descendants of member in specified hierarchy level
209
+ ```
210
+
211
+ See more examples of dimension and member queries in `spec/cube_spec.rb`.
212
+
213
+ ### User defined MDX functions
214
+
215
+ You can define new MDX functions using JavaScript, CoffeeScript or Ruby language that you can later use
216
+ either in calculated member formulas or in MDX queries. Here are examples of user defined functions in Ruby:
217
+
218
+ ```ruby
219
+ schema = Mondrian::OLAP::Schema.define do
220
+ # ... cube definitions ...
221
+ user_defined_function 'Factorial' do
222
+ ruby do
223
+ parameters :numeric
224
+ returns :numeric
225
+ def call(n)
226
+ n <= 1 ? 1 : n * call(n - 1)
227
+ end
228
+ end
229
+ end
230
+ user_defined_function 'UpperName' do
231
+ ruby do
232
+ parameters :member
233
+ returns :string
234
+ syntax :property
235
+ def call(member)
236
+ member.getName.upcase
237
+ end
238
+ end
239
+ end
240
+ end
241
+ ```
242
+
243
+ See more examples of user defined functions in `spec/schema_definition_spec.rb`.
244
+
245
+ ### Data access roles
246
+
247
+ In schema you can define data access roles which can be selected for connection and which will limit access just to
248
+ subset of measures and dimension members. Here is example of data access role definition:
249
+
250
+ ```ruby
251
+ schema = Mondrian::OLAP::Schema.define do
252
+ # ... cube definitions ...
253
+ role 'California manager' do
254
+ schema_grant :access => 'none' do
255
+ cube_grant :cube => 'Sales', :access => 'all' do
256
+ dimension_grant :dimension => '[Measures]', :access => 'all'
257
+ hierarchy_grant :hierarchy => '[Customers]', :access => 'custom',
258
+ :top_level => '[Customers].[State Province]', :bottom_level => '[Customers].[City]' do
259
+ member_grant :member => '[Customers].[USA].[CA]', :access => 'all'
260
+ member_grant :member => '[Customers].[USA].[CA].[Los Angeles]', :access => 'none'
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
266
+ ```
267
+
268
+ See more examples of data access roles in `spec/connection_role_spec.rb`.
269
+
270
+ REQUIREMENTS
271
+ ------------
272
+
273
+ mondrian-olap gem is compatible with JRuby versions 1.6 and 1.7 and Java 6 and 7 VM. mondrian-olap works only with JRuby and not with other Ruby implementations as it includes Mondrian OLAP Java libraries.
274
+
275
+ mondrian-olap currently supports MySQL, PostgreSQL, Oracle, LucidDB and Microsoft SQL Server databases. When using MySQL, PostgreSQL or LucidDB databases then install jdbc-mysql, jdbc-postgres or jdbc-luciddb gem and require "jdbc/mysql", "jdbc/postgres" or "jdbc/luciddb" to load the corresponding JDBC database driver. When using Oracle then include Oracle JDBC driver (`ojdbc6.jar` for Java 6) in `CLASSPATH` or copy to `JRUBY_HOME/lib` or require it in application manually. When using SQL Server you can choose between the jTDS or Microsoft JDBC drivers. If you use jTDS require "jdbc/jtds". If you use the Microsoft JDBC driver include `sqljdbc.jar` or `sqljdbc4.jar` in `CLASSPATH` or copy to `JRUBY_HOME/lib` or require it in application manually.
276
+
277
+ INSTALL
278
+ -------
279
+
280
+ Install gem with:
281
+
282
+ gem install mondrian-olap
283
+
284
+ or include in your project's Gemfile:
285
+
286
+ gem "mondrian-olap"
287
+
288
+ LINKS
289
+ -----
290
+
291
+ * Source code: http://github.com/rsim/mondrian-olap
292
+ * Bug reports / Feature requests: http://github.com/rsim/mondrian-olap/issues
293
+ * General discussions and questions at: http://groups.google.com/group/mondrian-olap
294
+ * mondrian-olap demo Rails application: https://github.com/rsim/mondrian_demo
295
+
296
+ LICENSE
297
+ -------
298
+
299
+ mondrian-olap is released under the terms of MIT license; see LICENSE.txt.
300
+
301
+ Mondrian OLAP Engine is released under the terms of the Eclipse Public
302
+ License v1.0 (EPL); see LICENSE-Mondrian.html.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.5.0
@@ -1,4 +1,5 @@
1
1
  require 'java'
2
+ require 'nokogiri'
2
3
 
3
4
  directory = File.expand_path("../jars", __FILE__)
4
5
  Dir["#{directory}/*.jar"].each do |file|
@@ -12,6 +13,6 @@ end
12
13
  # register Mondrian olap4j driver
13
14
  Java::mondrian.olap4j.MondrianOlap4jDriver
14
15
 
15
- %w(connection query result schema cube).each do |file|
16
+ %w(error connection query result schema schema_udf cube).each do |file|
16
17
  require "mondrian/olap/#{file}"
17
18
  end
@@ -17,35 +17,39 @@ module Mondrian
17
17
  end
18
18
 
19
19
  def connect
20
- # hack to call private constructor of MondrianOlap4jDriver
21
- # to avoid using DriverManager which fails to load JDBC drivers
22
- # because of not seeing JRuby required jar files
23
- cons = Java::MondrianOlap4j::MondrianOlap4jDriver.java_class.declared_constructor
24
- cons.accessible = true
25
- driver = cons.new_instance.to_java
26
-
27
- props = java.util.Properties.new
28
- props.setProperty('JdbcUser', @params[:username]) if @params[:username]
29
- props.setProperty('JdbcPassword', @params[:password]) if @params[:password]
30
-
31
- conn_string = connection_string
32
-
33
- # workaround for Mondrian ServiceDiscovery
34
- current_thread = Java::JavaLang::Thread.currentThread
35
- class_loader = current_thread.getContextClassLoader
36
- begin
37
- current_thread.setContextClassLoader(nil)
38
- @raw_jdbc_connection = driver.connect(conn_string, props)
39
- ensure
40
- current_thread.setContextClassLoader(class_loader)
41
- end
20
+ Error.wrap_native_exception do
21
+ # hack to call private constructor of MondrianOlap4jDriver
22
+ # to avoid using DriverManager which fails to load JDBC drivers
23
+ # because of not seeing JRuby required jar files
24
+ cons = Java::MondrianOlap4j::MondrianOlap4jDriver.java_class.declared_constructor
25
+ cons.accessible = true
26
+ driver = cons.new_instance.to_java
42
27
 
43
- @raw_connection = @raw_jdbc_connection.unwrap(Java::OrgOlap4j::OlapConnection.java_class)
44
- @raw_catalog = @raw_connection.getOlapCatalog
45
- # currently it is assumed that there is just one schema per connection catalog
46
- @raw_schema = @raw_catalog.getSchemas.first
47
- @connected = true
48
- true
28
+ props = java.util.Properties.new
29
+ props.setProperty('JdbcUser', @params[:username]) if @params[:username]
30
+ props.setProperty('JdbcPassword', @params[:password]) if @params[:password]
31
+
32
+ conn_string = connection_string
33
+
34
+ # latest Mondrian version added ClassResolver which uses current thread class loader to load some classes
35
+ # therefore need to set it to JRuby class loader to ensure that Mondrian classes are found
36
+ # (e.g. when running mondrian-olap inside OSGi container)
37
+ current_thread = Java::JavaLang::Thread.currentThread
38
+ class_loader = current_thread.getContextClassLoader
39
+ begin
40
+ current_thread.setContextClassLoader JRuby.runtime.jruby_class_loader
41
+ @raw_jdbc_connection = driver.connect(conn_string, props)
42
+ ensure
43
+ current_thread.setContextClassLoader(class_loader)
44
+ end
45
+
46
+ @raw_connection = @raw_jdbc_connection.unwrap(Java::OrgOlap4j::OlapConnection.java_class)
47
+ @raw_catalog = @raw_connection.getOlapCatalog
48
+ # currently it is assumed that there is just one schema per connection catalog
49
+ @raw_schema = @raw_catalog.getSchemas.first
50
+ @connected = true
51
+ true
52
+ end
49
53
  end
50
54
 
51
55
  def connected?
@@ -60,8 +64,17 @@ module Mondrian
60
64
  end
61
65
 
62
66
  def execute(query_string)
63
- statement = @raw_connection.prepareOlapStatement(query_string)
64
- Result.new(self, statement.executeQuery())
67
+ Error.wrap_native_exception do
68
+ statement = @raw_connection.prepareOlapStatement(query_string)
69
+ Result.new(self, statement.executeQuery())
70
+ end
71
+ end
72
+
73
+ def execute_drill_through(query_string)
74
+ Error.wrap_native_exception do
75
+ statement = @raw_connection.createStatement
76
+ Result::DrillThrough.new(statement.executeQuery(query_string))
77
+ end
65
78
  end
66
79
 
67
80
  def from(cube_name)
@@ -84,12 +97,120 @@ module Mondrian
84
97
  raw_cache_control.flushSchemaCache
85
98
  end
86
99
 
100
+ def available_role_names
101
+ @raw_connection.getAvailableRoleNames.to_a
102
+ end
103
+
104
+ def role_name
105
+ @raw_connection.getRoleName
106
+ end
107
+
108
+ def role_names
109
+ # workaround to access non-public method (was not public when using inside Torquebox)
110
+ # @raw_connection.getRoleNames.to_a
111
+ @raw_connection.java_method(:getRoleNames).call.to_a
112
+ end
113
+
114
+ def role_name=(name)
115
+ Error.wrap_native_exception do
116
+ @raw_connection.setRoleName(name)
117
+ end
118
+ end
119
+
120
+ def role_names=(names)
121
+ Error.wrap_native_exception do
122
+ # workaround to access non-public method (was not public when using inside Torquebox)
123
+ # @raw_connection.setRoleNames(Array(names))
124
+ @raw_connection.java_method(:setRoleNames, [Java::JavaUtil::List.java_class]).call(Array(names))
125
+ end
126
+ end
127
+
128
+ def locale
129
+ @raw_connection.getLocale.toString
130
+ end
131
+
132
+ def locale=(locale)
133
+ locale_elements = locale.to_s.split('_')
134
+ raise ArgumentError, "invalid locale string #{locale.inspect}" unless [1,2,3].include?(locale_elements.length)
135
+ java_locale = Java::JavaUtil::Locale.new(*locale_elements)
136
+ @raw_connection.setLocale(java_locale)
137
+ end
138
+
139
+ # access MondrianServer instance
140
+ def mondrian_server
141
+ Error.wrap_native_exception do
142
+ @raw_connection.getMondrianConnection.getServer
143
+ end
144
+ end
145
+
146
+ # Force shutdown of static MondrianServer, should not normally be used.
147
+ # Can be used in at_exit block if JRuby based plugin is unloaded from other Java application.
148
+ # WARNING: Mondrian will be unusable after calling this method!
149
+ def self.shutdown_static_mondrian_server!
150
+ static_mondrian_server = Java::MondrianOlap::MondrianServer.forId(nil)
151
+
152
+ # force Mondrian to think that static_mondrian_server is not static MondrianServer
153
+ mondrian_server_registry = Java::MondrianServer::MondrianServerRegistry::INSTANCE
154
+ f = mondrian_server_registry.java_class.declared_field("staticServer")
155
+ f.accessible = true
156
+ f.set_value(mondrian_server_registry, nil)
157
+
158
+ static_mondrian_server.shutdown
159
+
160
+ # shut down expiring reference timer thread
161
+ f = Java::MondrianUtil::ExpiringReference.java_class.declared_field("timer")
162
+ f.accessible = true
163
+ expiring_reference_timer = f.static_value.to_java
164
+ expiring_reference_timer.cancel
165
+
166
+ # shut down Mondrian Monitor
167
+ cons = Java::MondrianServer.__send__(:"MonitorImpl$ShutdownCommand").java_class.declared_constructor
168
+ cons.accessible = true
169
+ shutdown_command = cons.new_instance.to_java
170
+
171
+ cons = Java::MondrianServer.__send__(:"MonitorImpl$Handler").java_class.declared_constructor
172
+ cons.accessible = true
173
+ handler = cons.new_instance.to_java
174
+
175
+ pair = Java::mondrian.util.Pair.new handler, shutdown_command
176
+
177
+ f = Java::MondrianServer::MonitorImpl.java_class.declared_field("ACTOR")
178
+ f.accessible = true
179
+ monitor_actor = f.static_value.to_java
180
+
181
+ f = monitor_actor.java_class.declared_field("eventQueue")
182
+ f.accessible = true
183
+ event_queue = f.value(monitor_actor)
184
+
185
+ event_queue.put pair
186
+
187
+ # shut down connection pool thread
188
+ f = Java::mondrian.rolap.RolapConnectionPool.java_class.declared_field("instance")
189
+ f.accessible = true
190
+ rolap_connection_pool = f.static_value.to_java
191
+ f = rolap_connection_pool.java_class.declared_field("mapConnectKeyToPool")
192
+ f.accessible = true
193
+ map_connect_key_to_pool = f.value(rolap_connection_pool)
194
+ map_connect_key_to_pool.values.each do |pool|
195
+ pool.close if pool && !pool.isClosed
196
+ end
197
+
198
+ true
199
+ end
200
+
87
201
  private
88
202
 
89
203
  def connection_string
90
204
  string = "jdbc:mondrian:Jdbc=#{quote_string(jdbc_uri)};JdbcDrivers=#{jdbc_driver};"
91
205
  # by default use content checksum to reload schema when catalog has changed
92
206
  string << "UseContentChecksum=true;" unless @params[:use_content_checksum] == false
207
+ if role = @params[:role] || @params[:roles]
208
+ roles = Array(role).map{|r| r && r.to_s.gsub(',', ',,')}.compact
209
+ string << "Role=#{quote_string(roles.join(','))};" unless roles.empty?
210
+ end
211
+ if locale = @params[:locale]
212
+ string << "Locale=#{quote_string(locale.to_s)};"
213
+ end
93
214
  string << (@params[:catalog] ? "Catalog=#{catalog_uri}" : "CatalogContent=#{quote_string(catalog_content)}")
94
215
  end
95
216
 
@@ -98,14 +219,24 @@ module Mondrian
98
219
  when 'mysql', 'postgresql'
99
220
  uri = "jdbc:#{@driver}://#{@params[:host]}#{@params[:port] && ":#{@params[:port]}"}/#{@params[:database]}"
100
221
  uri << "?useUnicode=yes&characterEncoding=UTF-8" if @driver == 'mysql'
222
+ if (properties = @params[:properties]).is_a?(Hash) && !properties.empty?
223
+ uri << (@driver == 'mysql' ? '&' : '?')
224
+ uri << properties.map{|k, v| "#{k}=#{v}"}.join('&')
225
+ end
101
226
  uri
102
227
  when 'oracle'
103
228
  # connection using TNS alias
104
229
  if @params[:database] && !@params[:host] && !@params[:url] && ENV['TNS_ADMIN']
105
230
  "jdbc:oracle:thin:@#{@params[:database]}"
106
231
  else
107
- @params[:url] ||
108
- "jdbc:oracle:thin:@#{@params[:host] || 'localhost'}:#{@params[:port] || 1521}:#{@params[:database]}"
232
+ @params[:url] || begin
233
+ database = @params[:database]
234
+ unless database =~ %r{^(:|/)}
235
+ # assume database is a SID if no colon or slash are supplied (backward-compatibility)
236
+ database = ":#{database}"
237
+ end
238
+ "jdbc:oracle:thin:@#{@params[:host] || 'localhost'}:#{@params[:port] || 1521}#{database}"
239
+ end
109
240
  end
110
241
  when 'luciddb'
111
242
  uri = "jdbc:luciddb:http://#{@params[:host]}#{@params[:port] && ":#{@params[:port]}"}"