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.
- checksums.yaml +7 -0
- data/Changelog.md +38 -0
- data/LICENSE.txt +1 -1
- data/README.md +302 -0
- data/VERSION +1 -1
- data/lib/mondrian/jars/commons-collections-3.2.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.1.1.jar +0 -0
- data/lib/mondrian/jars/commons-math-1.1.jar +0 -0
- data/lib/mondrian/jars/eigenbase-properties-1.1.2.jar +0 -0
- data/lib/mondrian/jars/eigenbase-resgen-1.3.1.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom-1.3.1.jar +0 -0
- data/lib/mondrian/jars/{javacup.jar → javacup-10k.jar} +0 -0
- data/lib/mondrian/jars/log4j-1.2.14.jar +0 -0
- data/lib/mondrian/jars/mondrian.jar +0 -0
- data/lib/mondrian/jars/olap4j-1.0.1.539.jar +0 -0
- data/lib/mondrian/olap.rb +2 -1
- data/lib/mondrian/olap/connection.rb +163 -32
- data/lib/mondrian/olap/cube.rb +163 -24
- data/lib/mondrian/olap/error.rb +57 -0
- data/lib/mondrian/olap/query.rb +52 -17
- data/lib/mondrian/olap/result.rb +298 -6
- data/lib/mondrian/olap/schema.rb +220 -29
- data/lib/mondrian/olap/schema_element.rb +31 -11
- data/lib/mondrian/olap/schema_udf.rb +331 -0
- data/lib/mondrian/olap/version.rb +5 -0
- data/spec/connection_role_spec.rb +130 -0
- data/spec/connection_spec.rb +36 -1
- data/spec/cube_spec.rb +137 -7
- data/spec/fixtures/MondrianTest.xml +4 -4
- data/spec/mondrian_spec.rb +53 -0
- data/spec/query_spec.rb +294 -11
- data/spec/rake_tasks.rb +8 -8
- data/spec/schema_definition_spec.rb +845 -26
- data/spec/spec_helper.rb +26 -17
- data/spec/support/matchers/be_like.rb +2 -2
- metadata +296 -237
- data/.rspec +0 -2
- data/Gemfile +0 -18
- data/README.rdoc +0 -221
- data/RUNNING_TESTS.rdoc +0 -66
- data/Rakefile +0 -46
- data/lib/mondrian/jars/commons-collections-3.1.jar +0 -0
- data/lib/mondrian/jars/commons-logging-1.0.4.jar +0 -0
- data/lib/mondrian/jars/commons-math-1.0.jar +0 -0
- data/lib/mondrian/jars/eigenbase-properties.jar +0 -0
- data/lib/mondrian/jars/eigenbase-resgen.jar +0 -0
- data/lib/mondrian/jars/eigenbase-xom.jar +0 -0
- data/lib/mondrian/jars/log4j-1.2.8.jar +0 -0
- data/lib/mondrian/jars/olap4j.jar +0 -0
- data/mondrian-olap.gemspec +0 -126
checksums.yaml
ADDED
@@ -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
|
data/Changelog.md
CHANGED
@@ -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
|
data/LICENSE.txt
CHANGED
data/README.md
ADDED
@@ -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.
|
1
|
+
0.5.0
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
File without changes
|
Binary file
|
Binary file
|
Binary file
|
data/lib/mondrian/olap.rb
CHANGED
@@ -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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
64
|
-
|
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
|
-
|
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]}"}"
|