mondrian-olap 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +15 -0
- data/LICENSE.txt +1 -1
- data/README.md +40 -40
- data/VERSION +1 -1
- data/lib/mondrian/jars/commons-collections-3.2.1.jar +0 -0
- data/lib/mondrian/jars/commons-dbcp-1.4.jar +0 -0
- data/lib/mondrian/jars/commons-io-2.2.jar +0 -0
- data/lib/mondrian/jars/commons-pool-1.5.7.jar +0 -0
- data/lib/mondrian/jars/commons-vfs-20100924-pentaho.jar +0 -0
- data/lib/mondrian/jars/log4j-1.2.17.jar +0 -0
- data/lib/mondrian/jars/mondrian-3.8.0.0-209.jar +0 -0
- data/lib/mondrian/jars/olap4j-1.2.0.jar +0 -0
- data/lib/mondrian/olap/connection.rb +49 -2
- data/lib/mondrian/olap/cube.rb +16 -0
- data/lib/mondrian/olap/error.rb +1 -1
- data/lib/mondrian/olap/query.rb +19 -4
- data/lib/mondrian/olap/result.rb +6 -0
- data/lib/mondrian/olap/schema.rb +39 -3
- data/spec/connection_spec.rb +1 -1
- data/spec/cube_spec.rb +32 -2
- data/spec/query_spec.rb +82 -4
- data/spec/rake_tasks.rb +2 -2
- data/spec/schema_definition_spec.rb +155 -0
- data/spec/spec_helper.rb +28 -10
- metadata +10 -9
- data/lib/mondrian/jars/commons-collections-3.2.jar +0 -0
- data/lib/mondrian/jars/commons-dbcp-1.2.1.jar +0 -0
- data/lib/mondrian/jars/commons-pool-1.2.jar +0 -0
- data/lib/mondrian/jars/commons-vfs-1.0.jar +0 -0
- data/lib/mondrian/jars/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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33377438a5e2533d61b3304824a4e0aefe545f68
|
4
|
+
data.tar.gz: 13ef2ecd466795a07f43252231e4e7ad586c1e45
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c0ac6bc10f6ed6b4725d0991131402735e50c9c4cbc3de016a8714c7ceca5ce420ce9b5090fe006ae5495f0e8add0cf4f4cbb773b2f3da35767481e60b04a7d4
|
7
|
+
data.tar.gz: b7a47e4872dd58434b05e8d07b99d06d73d1ef023be953b4be00a5de2bd6c18db6e943496cecf1b3cad0fab60a5e3850b1084b7e31896533df3d1e6f2112d03c
|
data/Changelog.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
### 0.6.0 / 2014-11-10
|
2
|
+
|
3
|
+
* New features
|
4
|
+
* upgraded to latest Mondrian 3.8.0 version
|
5
|
+
* connection with generic JDBC driver using jdbc_driver and jdbc_url parameters
|
6
|
+
* added hierarchy and parent attributes for calculated member schema definition element
|
7
|
+
* added visible attribute for cube, dimension, virtual_cube_dimension, hierarchy and level schema definition elements
|
8
|
+
* added query builder generate method
|
9
|
+
* added schema parameters and query execution with parameters
|
10
|
+
* updated specs to pass on Java 8
|
11
|
+
* Improvements
|
12
|
+
* set defaultRowPrefetch property for Oracle connection
|
13
|
+
* Bug fixes
|
14
|
+
* fixed result drill_through method with just all members selection
|
15
|
+
|
1
16
|
### 0.5.0 / 2013-11-29
|
2
17
|
|
3
18
|
* New features
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -12,7 +12,7 @@ One of the most popular open-source OLAP engines is [Mondrian](http://mondrian.p
|
|
12
12
|
|
13
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
14
|
|
15
|
-
mondrian-olap is used in [eazyBI data analysis and reporting web application](https://eazybi.com). [eazyBI
|
15
|
+
mondrian-olap is used in [eazyBI data analysis and reporting web application](https://eazybi.com). [Private eazyBI](https://eazybi.com/help/private-eazybi) 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
16
|
|
17
17
|
USAGE
|
18
18
|
-----
|
@@ -56,42 +56,42 @@ require "mondrian-olap"
|
|
56
56
|
schema = Mondrian::OLAP::Schema.define do
|
57
57
|
cube 'Sales' do
|
58
58
|
table 'sales'
|
59
|
-
dimension 'Customers', :
|
60
|
-
hierarchy :
|
59
|
+
dimension 'Customers', foreign_key: 'customer_id' do
|
60
|
+
hierarchy has_all: true, all_member_name: 'All Customers', primary_key: 'id' do
|
61
61
|
table 'customers'
|
62
|
-
level 'Country', :
|
63
|
-
level 'State Province', :
|
64
|
-
level 'City', :
|
65
|
-
level 'Name', :
|
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
66
|
end
|
67
67
|
end
|
68
|
-
dimension 'Products', :
|
69
|
-
hierarchy :
|
70
|
-
:
|
71
|
-
join :
|
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
72
|
table 'products'
|
73
73
|
table 'product_classes'
|
74
74
|
end
|
75
|
-
level 'Product Family', :
|
76
|
-
level 'Brand Name', :
|
77
|
-
level 'Product Name', :
|
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
78
|
end
|
79
79
|
end
|
80
|
-
dimension 'Time', :
|
81
|
-
hierarchy :
|
80
|
+
dimension 'Time', foreign_key: 'time_id', type: 'TimeDimension' do
|
81
|
+
hierarchy has_all: false, primary_key: 'id' do
|
82
82
|
table 'time'
|
83
|
-
level 'Year', :
|
84
|
-
level 'Quarter', :
|
85
|
-
level 'Month', :
|
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
86
|
end
|
87
|
-
hierarchy 'Weekly', :
|
87
|
+
hierarchy 'Weekly', has_all: false, primary_key: 'id' do
|
88
88
|
table 'time'
|
89
|
-
level 'Year', :
|
90
|
-
level 'Week', :
|
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
91
|
end
|
92
92
|
end
|
93
|
-
measure 'Unit Sales', :
|
94
|
-
measure 'Store Sales', :
|
93
|
+
measure 'Unit Sales', column: 'unit_sales', aggregator: 'sum'
|
94
|
+
measure 'Store Sales', column: 'store_sales', aggregator: 'sum'
|
95
95
|
end
|
96
96
|
end
|
97
97
|
```
|
@@ -104,12 +104,12 @@ When schema is defined it is necessary to establish OLAP connection to database.
|
|
104
104
|
require "jdbc/mysql"
|
105
105
|
|
106
106
|
olap = Mondrian::OLAP::Connection.create(
|
107
|
-
:
|
108
|
-
:
|
109
|
-
:
|
110
|
-
:
|
111
|
-
:
|
112
|
-
:
|
107
|
+
driver: 'mysql',
|
108
|
+
host: 'localhost,
|
109
|
+
database: 'mondrian_test',
|
110
|
+
username: 'mondrian_user',
|
111
|
+
password: 'secret',
|
112
|
+
schema: schema
|
113
113
|
)
|
114
114
|
```
|
115
115
|
|
@@ -171,7 +171,7 @@ Here is example of more complex query "Get sales amount and profit % of top 50 p
|
|
171
171
|
olap.from('Sales').
|
172
172
|
with_member('[Measures].[ProfitPct]').
|
173
173
|
as('Val((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',
|
174
|
-
:
|
174
|
+
format_string: 'Percent').
|
175
175
|
columns('[Measures].[Store Sales]', '[Measures].[ProfitPct]').
|
176
176
|
rows('[Products].children').crossjoin('[Customers].[Canada]', '[Customers].[USA]').
|
177
177
|
top_count(50, '[Measures].[Store Sales]').
|
@@ -251,13 +251,13 @@ subset of measures and dimension members. Here is example of data access role de
|
|
251
251
|
schema = Mondrian::OLAP::Schema.define do
|
252
252
|
# ... cube definitions ...
|
253
253
|
role 'California manager' do
|
254
|
-
schema_grant :
|
255
|
-
cube_grant :
|
256
|
-
dimension_grant :
|
257
|
-
hierarchy_grant :
|
258
|
-
:
|
259
|
-
member_grant :
|
260
|
-
member_grant :
|
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
261
|
end
|
262
262
|
end
|
263
263
|
end
|
@@ -270,9 +270,9 @@ See more examples of data access roles in `spec/connection_role_spec.rb`.
|
|
270
270
|
REQUIREMENTS
|
271
271
|
------------
|
272
272
|
|
273
|
-
mondrian-olap gem is compatible with JRuby
|
273
|
+
mondrian-olap gem is compatible with JRuby version 1.7 and Java 6, 7 or 8 VM. mondrian-olap works only with JRuby and not with other Ruby implementations as it includes Mondrian OLAP Java libraries.
|
274
274
|
|
275
|
-
mondrian-olap
|
275
|
+
mondrian-olap supports MySQL, PostgreSQL, Oracle, LucidDB and Microsoft SQL Server databases as well as other databases that are supported by Mondrian OLAP engine (using jdbc_driver and jdbc_url connection parameters). 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
276
|
|
277
277
|
INSTALL
|
278
278
|
-------
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.0
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -7,7 +7,7 @@ module Mondrian
|
|
7
7
|
connection
|
8
8
|
end
|
9
9
|
|
10
|
-
attr_reader :raw_connection, :raw_catalog, :raw_schema
|
10
|
+
attr_reader :raw_connection, :raw_catalog, :raw_schema, :raw_schema_reader
|
11
11
|
|
12
12
|
def initialize(params={})
|
13
13
|
@params = params
|
@@ -29,6 +29,13 @@ module Mondrian
|
|
29
29
|
props.setProperty('JdbcUser', @params[:username]) if @params[:username]
|
30
30
|
props.setProperty('JdbcPassword', @params[:password]) if @params[:password]
|
31
31
|
|
32
|
+
# on Oracle increase default row prefetch size
|
33
|
+
# as default 10 is very low and slows down loading of all dimension members
|
34
|
+
if @driver == 'oracle'
|
35
|
+
prefetch_rows = @params[:prefetch_rows] || 100
|
36
|
+
props.setProperty("jdbc.defaultRowPrefetch", prefetch_rows.to_s)
|
37
|
+
end
|
38
|
+
|
32
39
|
conn_string = connection_string
|
33
40
|
|
34
41
|
# latest Mondrian version added ClassResolver which uses current thread class loader to load some classes
|
@@ -47,6 +54,7 @@ module Mondrian
|
|
47
54
|
@raw_catalog = @raw_connection.getOlapCatalog
|
48
55
|
# currently it is assumed that there is just one schema per connection catalog
|
49
56
|
@raw_schema = @raw_catalog.getSchemas.first
|
57
|
+
@raw_schema_reader = @raw_connection.getMondrianConnection.getSchemaReader
|
50
58
|
@connected = true
|
51
59
|
true
|
52
60
|
end
|
@@ -63,13 +71,21 @@ module Mondrian
|
|
63
71
|
true
|
64
72
|
end
|
65
73
|
|
66
|
-
def execute(query_string)
|
74
|
+
def execute(query_string, parameters = {})
|
67
75
|
Error.wrap_native_exception do
|
68
76
|
statement = @raw_connection.prepareOlapStatement(query_string)
|
77
|
+
set_statement_parameters(statement, parameters)
|
69
78
|
Result.new(self, statement.executeQuery())
|
70
79
|
end
|
71
80
|
end
|
72
81
|
|
82
|
+
# access mondrian.olap.Parameter object
|
83
|
+
def mondrian_parameter(parameter_name)
|
84
|
+
Error.wrap_native_exception do
|
85
|
+
@raw_schema_reader.getParameter(parameter_name)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
73
89
|
def execute_drill_through(query_string)
|
74
90
|
Error.wrap_native_exception do
|
75
91
|
statement = @raw_connection.createStatement
|
@@ -195,6 +211,14 @@ module Mondrian
|
|
195
211
|
pool.close if pool && !pool.isClosed
|
196
212
|
end
|
197
213
|
|
214
|
+
# unregister MBean
|
215
|
+
mbs = Java::JavaLangManagement::ManagementFactory.getPlatformMBeanServer
|
216
|
+
mbean_name = Java::JavaxManagement::ObjectName.new("mondrian.server:type=Server-#{static_mondrian_server.getId}")
|
217
|
+
begin
|
218
|
+
mbs.unregisterMBean(mbean_name)
|
219
|
+
rescue Java::JavaxManagement::InstanceNotFoundException
|
220
|
+
end
|
221
|
+
|
198
222
|
true
|
199
223
|
end
|
200
224
|
|
@@ -255,6 +279,8 @@ module Mondrian
|
|
255
279
|
uri << ";applicationName=#{@params[:application_name]}" if @params[:application_name]
|
256
280
|
uri << ";instanceName=#{@params[:instance_name]}" if @params[:instance_name]
|
257
281
|
uri
|
282
|
+
when 'jdbc'
|
283
|
+
@params[:jdbc_url] or raise ArgumentError, 'missing jdbc_url parameter'
|
258
284
|
else
|
259
285
|
raise ArgumentError, 'unknown JDBC driver'
|
260
286
|
end
|
@@ -274,6 +300,8 @@ module Mondrian
|
|
274
300
|
'net.sourceforge.jtds.jdbc.Driver'
|
275
301
|
when 'sqlserver'
|
276
302
|
'com.microsoft.sqlserver.jdbc.SQLServerDriver'
|
303
|
+
when 'jdbc'
|
304
|
+
@params[:jdbc_driver] or raise ArgumentError, 'missing jdbc_driver parameter'
|
277
305
|
else
|
278
306
|
raise ArgumentError, 'unknown JDBC driver'
|
279
307
|
end
|
@@ -301,6 +329,25 @@ module Mondrian
|
|
301
329
|
"'#{string.gsub("'","''")}'"
|
302
330
|
end
|
303
331
|
|
332
|
+
def set_statement_parameters(statement, parameters)
|
333
|
+
if parameters && !parameters.empty?
|
334
|
+
# define addtional parameters which can be accessed from user defined functions
|
335
|
+
if parameters[:define_parameters]
|
336
|
+
parameters = parameters.dup
|
337
|
+
define_parameters = parameters.delete(:define_parameters)
|
338
|
+
query_validator = statement.getQuery.createValidator
|
339
|
+
define_parameters.each do |dp_name, dp_value|
|
340
|
+
dp_type_class = dp_value.is_a?(Numeric) ? Java::MondrianOlapType::NumericType : Java::MondrianOlapType::StringType
|
341
|
+
query_validator.createOrLookupParam(true, dp_name, dp_type_class.new, nil, nil)
|
342
|
+
parameters[dp_name] = dp_value
|
343
|
+
end
|
344
|
+
end
|
345
|
+
parameters.each do |parameter_name, value|
|
346
|
+
statement.getQuery.setParameter(parameter_name, value)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
304
351
|
end
|
305
352
|
end
|
306
353
|
end
|
data/lib/mondrian/olap/cube.rb
CHANGED
@@ -46,6 +46,10 @@ module Mondrian
|
|
46
46
|
annotations_for(@raw_cube)
|
47
47
|
end
|
48
48
|
|
49
|
+
def visible?
|
50
|
+
@raw_cube.isVisible
|
51
|
+
end
|
52
|
+
|
49
53
|
def dimensions
|
50
54
|
@dimenstions ||= @raw_cube.getDimensions.map{|d| Dimension.new(self, d)}
|
51
55
|
end
|
@@ -132,6 +136,10 @@ module Mondrian
|
|
132
136
|
annotations_for(@raw_dimension)
|
133
137
|
end
|
134
138
|
|
139
|
+
def visible?
|
140
|
+
@raw_dimension.isVisible
|
141
|
+
end
|
142
|
+
|
135
143
|
end
|
136
144
|
|
137
145
|
class Hierarchy
|
@@ -207,6 +215,10 @@ module Mondrian
|
|
207
215
|
annotations_for(@raw_hierarchy)
|
208
216
|
end
|
209
217
|
|
218
|
+
def visible?
|
219
|
+
@raw_hierarchy.isVisible
|
220
|
+
end
|
221
|
+
|
210
222
|
end
|
211
223
|
|
212
224
|
class Level
|
@@ -260,6 +272,10 @@ module Mondrian
|
|
260
272
|
annotations_for(@raw_level)
|
261
273
|
end
|
262
274
|
|
275
|
+
def visible?
|
276
|
+
@raw_level.isVisible
|
277
|
+
end
|
278
|
+
|
263
279
|
end
|
264
280
|
|
265
281
|
class Member
|
data/lib/mondrian/olap/error.rb
CHANGED
@@ -44,7 +44,7 @@ module Mondrian
|
|
44
44
|
bt = @native_error.backtrace
|
45
45
|
if @root_cause
|
46
46
|
root_cause_bt = Array(@root_cause.backtrace)
|
47
|
-
root_cause_bt[0,
|
47
|
+
root_cause_bt[0,10].reverse.each do |bt_line|
|
48
48
|
bt.unshift "root cause: #{bt_line}"
|
49
49
|
end
|
50
50
|
bt.unshift "root cause: #{@root_cause.java_class.name}: #{@root_cause.message.chomp}"
|
data/lib/mondrian/olap/query.rb
CHANGED
@@ -47,7 +47,7 @@ module Mondrian
|
|
47
47
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
48
48
|
def #{method}(*axis_members)
|
49
49
|
raise ArgumentError, "cannot use #{method} method before axis or with_set method" unless @current_set
|
50
|
-
raise ArgumentError, "specify
|
50
|
+
raise ArgumentError, "specify set of members for #{method} method" if axis_members.empty?
|
51
51
|
members = axis_members.length == 1 && axis_members[0].is_a?(Array) ? axis_members[0] : axis_members
|
52
52
|
@current_set.replace [:#{method}, @current_set.clone, members]
|
53
53
|
self
|
@@ -57,7 +57,7 @@ module Mondrian
|
|
57
57
|
|
58
58
|
def except(*axis_members)
|
59
59
|
raise ArgumentError, "cannot use except method before axis or with_set method" unless @current_set
|
60
|
-
raise ArgumentError, "specify
|
60
|
+
raise ArgumentError, "specify set of members for except method" if axis_members.empty?
|
61
61
|
members = axis_members.length == 1 && axis_members[0].is_a?(Array) ? axis_members[0] : axis_members
|
62
62
|
if [:crossjoin, :nonempty_crossjoin].include? @current_set[0]
|
63
63
|
@current_set[2] = [:except, @current_set[2], members]
|
@@ -87,6 +87,19 @@ module Mondrian
|
|
87
87
|
self
|
88
88
|
end
|
89
89
|
|
90
|
+
def generate(*axis_members)
|
91
|
+
raise ArgumentError, "cannot use generate method before axis or with_set method" unless @current_set
|
92
|
+
all = if axis_members.last == :all
|
93
|
+
axis_members.pop
|
94
|
+
'ALL'
|
95
|
+
end
|
96
|
+
raise ArgumentError, "specify set of members for generate method" if axis_members.empty?
|
97
|
+
members = axis_members.length == 1 && axis_members[0].is_a?(Array) ? axis_members[0] : axis_members
|
98
|
+
@current_set.replace [:generate, @current_set.clone, members]
|
99
|
+
@current_set << all if all
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
90
103
|
VALID_ORDERS = ['ASC', 'BASC', 'DESC', 'BDESC']
|
91
104
|
|
92
105
|
def order(expression, direction)
|
@@ -216,9 +229,9 @@ module Mondrian
|
|
216
229
|
mdx
|
217
230
|
end
|
218
231
|
|
219
|
-
def execute
|
232
|
+
def execute(parameters = {})
|
220
233
|
Error.wrap_native_exception do
|
221
|
-
@connection.execute to_mdx
|
234
|
+
@connection.execute to_mdx, parameters
|
222
235
|
end
|
223
236
|
end
|
224
237
|
|
@@ -299,6 +312,8 @@ module Mondrian
|
|
299
312
|
when :filter
|
300
313
|
as_alias = members[3] ? " AS #{members[3]}" : nil
|
301
314
|
"FILTER(#{members_to_mdx(members[1])}#{as_alias}, #{members[2]})"
|
315
|
+
when :generate
|
316
|
+
"GENERATE(#{members_to_mdx(members[1])}, #{members_to_mdx(members[2])}#{members[3] && ", #{members[3]}"})"
|
302
317
|
when :order
|
303
318
|
"ORDER(#{members_to_mdx(members[1])}, #{expression_to_mdx(members[2])}, #{members[3]})"
|
304
319
|
when :top_count, :bottom_count
|
data/lib/mondrian/olap/result.rb
CHANGED
@@ -8,6 +8,8 @@ module Mondrian
|
|
8
8
|
@raw_cell_set = raw_cell_set
|
9
9
|
end
|
10
10
|
|
11
|
+
attr_reader :raw_cell_set
|
12
|
+
|
11
13
|
def axes_count
|
12
14
|
axes.length
|
13
15
|
end
|
@@ -248,6 +250,10 @@ module Mondrian
|
|
248
250
|
if sql_non_extended =~ /\Aselect (.*) from (.*) where (.*) order by (.*)\Z/
|
249
251
|
non_extended_from = $2
|
250
252
|
non_extended_where = $3
|
253
|
+
# if drill through total measure with just all members selection
|
254
|
+
elsif sql_non_extended =~ /\Aselect (.*) from (.*)\Z/
|
255
|
+
non_extended_from = $2
|
256
|
+
non_extended_where = "1 = 1" # dummy true condition
|
251
257
|
else
|
252
258
|
raise ArgumentError, "cannot parse drill through SQL: #{sql_non_extended}"
|
253
259
|
end
|
data/lib/mondrian/olap/schema.rb
CHANGED
@@ -50,10 +50,12 @@ module Mondrian
|
|
50
50
|
public
|
51
51
|
|
52
52
|
attributes :name, :description, :measures_caption
|
53
|
-
elements :annotations, :dimension, :cube, :virtual_cube, :role, :user_defined_function
|
53
|
+
elements :annotations, :parameter, :dimension, :cube, :virtual_cube, :role, :user_defined_function
|
54
54
|
|
55
55
|
class Cube < SchemaElement
|
56
56
|
attributes :name, :description, :caption,
|
57
|
+
# Whether this cube is visible in the user-interface. Default true.
|
58
|
+
:visible,
|
57
59
|
# The name of the measure that would be taken as the default measure of the cube.
|
58
60
|
:default_measure,
|
59
61
|
# Should the Fact table data for this Cube be cached by Mondrian or not.
|
@@ -84,6 +86,8 @@ module Mondrian
|
|
84
86
|
|
85
87
|
class Dimension < SchemaElement
|
86
88
|
attributes :name, :description, :caption,
|
89
|
+
# Whether this dimension is visible in the user-interface. Default true.
|
90
|
+
:visible,
|
87
91
|
# The dimension's type may be one of "Standard" or "Time".
|
88
92
|
# A time dimension will allow the use of the MDX time functions (WTD, YTD, QTD, etc.).
|
89
93
|
# Use a standard dimension if the dimension is not a time-related dimension.
|
@@ -121,6 +125,8 @@ module Mondrian
|
|
121
125
|
|
122
126
|
class Hierarchy < SchemaElement
|
123
127
|
attributes :name, :description, :caption,
|
128
|
+
# Whether this hierarchy is visible in the user-interface. Default true.
|
129
|
+
:visible,
|
124
130
|
# Whether this hierarchy has an 'all' member.
|
125
131
|
:has_all,
|
126
132
|
# Name of the 'all' member. If this attribute is not specified,
|
@@ -137,6 +143,8 @@ module Mondrian
|
|
137
143
|
# The name of the table which contains primary_key.
|
138
144
|
# If the hierarchy has only one table, defaults to that; it is required.
|
139
145
|
:primary_key_table,
|
146
|
+
#
|
147
|
+
:default_member,
|
140
148
|
# Should be set to the level (if such a level exists) at which depth it is known
|
141
149
|
# that all members have entirely unique rows, allowing SQL GROUP BY clauses to be completely eliminated from the query.
|
142
150
|
:unique_key_level_name
|
@@ -160,6 +168,8 @@ module Mondrian
|
|
160
168
|
|
161
169
|
class Level < SchemaElement
|
162
170
|
attributes :name, :description, :caption,
|
171
|
+
# Whether this level is visible in the user-interface. Default true.
|
172
|
+
:visible,
|
163
173
|
# The name of the table that the column comes from.
|
164
174
|
# If this hierarchy is based upon just one table, defaults to the name of that table;
|
165
175
|
# otherwise, it is required.
|
@@ -187,6 +197,12 @@ module Mondrian
|
|
187
197
|
# for example, "DATE '2006-06-01'".
|
188
198
|
# Default value: 'String'
|
189
199
|
:type,
|
200
|
+
# Indicates the Java type that Mondrian uses to store this level's key column.
|
201
|
+
# It also determines the JDBC method that Mondrian will call to retrieve the column;
|
202
|
+
# for example, if the Java type is 'int', Mondrian will call 'ResultSet.getInt(int)'.
|
203
|
+
# Usually this attribute is not needed, because Mondrian can choose a sensible type based on the type of the database column.
|
204
|
+
# Allowable values are: 'int', 'long', 'Object', 'String'.
|
205
|
+
:internal_type,
|
190
206
|
# Whether members are unique across all parents.
|
191
207
|
# For example, zipcodes are unique across all states.
|
192
208
|
# The first level's members are always unique.
|
@@ -288,8 +304,12 @@ module Mondrian
|
|
288
304
|
|
289
305
|
class CalculatedMember < SchemaElement
|
290
306
|
attributes :name, :description, :caption,
|
291
|
-
# Name of the dimension which this member belongs to.
|
307
|
+
# Name of the dimension which this member belongs to. Cannot be used if :hieararchy is specified.
|
292
308
|
:dimension,
|
309
|
+
# Full unique name of the hierarchy that this member belongs to.
|
310
|
+
:hierarchy,
|
311
|
+
# Fully-qualified name of the parent member. If not specified, the member will be at the lowest level (besides the 'all' level) in the hierarchy.
|
312
|
+
:parent,
|
293
313
|
# Format string with which to format cells of this measure. For more details, see the mondrian.util.Format class.
|
294
314
|
:format_string,
|
295
315
|
# Whether this member is visible in the user-interface. Default true.
|
@@ -312,6 +332,8 @@ module Mondrian
|
|
312
332
|
|
313
333
|
class VirtualCube < SchemaElement
|
314
334
|
attributes :name, :description, :caption,
|
335
|
+
# Whether this cube is visible in the user-interface. Default true.
|
336
|
+
:visible,
|
315
337
|
# The name of the measure that would be taken as the default measure of the cube.
|
316
338
|
:default_measure,
|
317
339
|
# Whether element is enabled - if true, then the VirtualCube is realized otherwise it is ignored.
|
@@ -322,7 +344,9 @@ module Mondrian
|
|
322
344
|
class VirtualCubeDimension < SchemaElement
|
323
345
|
attributes :name,
|
324
346
|
# Name of the cube which the dimension belongs to, or unspecified if the dimension is shared
|
325
|
-
:cube_name
|
347
|
+
:cube_name,
|
348
|
+
# Whether this dimension is visible in the user-interface. Default true.
|
349
|
+
:visible
|
326
350
|
end
|
327
351
|
|
328
352
|
class VirtualCubeMeasure < SchemaElement
|
@@ -473,6 +497,18 @@ module Mondrian
|
|
473
497
|
class Annotation < SchemaElement
|
474
498
|
content :text
|
475
499
|
end
|
500
|
+
|
501
|
+
class Parameter < SchemaElement
|
502
|
+
attributes :name, :description,
|
503
|
+
# Indicates the type of this parameter: String, Numeric, Integer, Boolean, Date, Time, Timestamp, or Member.
|
504
|
+
:type,
|
505
|
+
# If false, statement cannot change the value of this parameter; the parameter becomes effectively constant
|
506
|
+
# (provided that its default value expression always returns the same value). Default is true.
|
507
|
+
:modifiable,
|
508
|
+
# Expression for the default value of this parameter.
|
509
|
+
:default_value
|
510
|
+
end
|
511
|
+
|
476
512
|
end
|
477
513
|
end
|
478
514
|
end
|
data/spec/connection_spec.rb
CHANGED
@@ -45,7 +45,7 @@ describe "Connection" do
|
|
45
45
|
schema_field = @olap.raw_schema.getClass.getDeclaredField("schema")
|
46
46
|
schema_field.setAccessible(true)
|
47
47
|
private_schema = schema_field.get(@olap.raw_schema)
|
48
|
-
private_schema.getDialect.java_class.name.should == case MONDRIAN_DRIVER
|
48
|
+
private_schema.getDialect.java_class.name.should == case MONDRIAN_DRIVER.split('_').last
|
49
49
|
when 'mysql' then 'mondrian.spi.impl.MySqlDialect'
|
50
50
|
when 'postgresql' then 'mondrian.spi.impl.PostgreSqlDialect'
|
51
51
|
when 'oracle' then 'mondrian.spi.impl.OracleDialect'
|
data/spec/cube_spec.rb
CHANGED
@@ -10,17 +10,21 @@ describe "Cube" do
|
|
10
10
|
caption 'Sales caption'
|
11
11
|
annotations :foo => 'bar'
|
12
12
|
table 'sales'
|
13
|
+
visible true
|
13
14
|
dimension 'Gender', :foreign_key => 'customer_id' do
|
14
15
|
description 'Gender description'
|
15
16
|
caption 'Gender caption'
|
17
|
+
visible true
|
16
18
|
hierarchy :has_all => true, :primary_key => 'id' do
|
17
19
|
description 'Gender hierarchy description'
|
18
20
|
caption 'Gender hierarchy caption'
|
19
21
|
all_member_name 'All Genders'
|
20
22
|
all_member_caption 'All Genders caption'
|
21
23
|
table 'customers'
|
24
|
+
visible true
|
22
25
|
level 'Gender', :column => 'gender', :unique_members => true,
|
23
26
|
:description => 'Gender level description', :caption => 'Gender level caption' do
|
27
|
+
visible true
|
24
28
|
# Dimension values SQL generated by caption_expression fails on PostgreSQL and MS SQL
|
25
29
|
if %w(mysql oracle).include?(MONDRIAN_DRIVER)
|
26
30
|
caption_expression do
|
@@ -56,6 +60,10 @@ describe "Cube" do
|
|
56
60
|
level 'Week', :column => 'weak_of_year', :type => 'Numeric', :unique_members => false, :level_type => 'TimeWeeks'
|
57
61
|
end
|
58
62
|
end
|
63
|
+
calculated_member 'Last week' do
|
64
|
+
hierarchy '[Time.Weekly]'
|
65
|
+
formula 'Tail([Time.Weekly].[Week].Members).Item(0)'
|
66
|
+
end
|
59
67
|
measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum', :annotations => {:foo => 'bar'}
|
60
68
|
measure 'Store Sales', :column => 'store_sales', :aggregator => 'sum'
|
61
69
|
measure 'Store Cost', :column => 'store_cost', :aggregator => 'sum', :visible => false
|
@@ -92,6 +100,10 @@ describe "Cube" do
|
|
92
100
|
@olap.cube('Sales').annotations.should == {'foo' => 'bar'}
|
93
101
|
end
|
94
102
|
|
103
|
+
it "should be visible" do
|
104
|
+
@olap.cube('Sales').should be_visible
|
105
|
+
end
|
106
|
+
|
95
107
|
describe "dimensions" do
|
96
108
|
before(:all) do
|
97
109
|
@cube = @olap.cube('Sales')
|
@@ -147,6 +159,11 @@ describe "Cube" do
|
|
147
159
|
it "should get dimension empty annotations" do
|
148
160
|
@cube.dimension('Gender').annotations.should == {}
|
149
161
|
end
|
162
|
+
|
163
|
+
it "should be visible" do
|
164
|
+
@cube.dimension('Gender').should be_visible
|
165
|
+
end
|
166
|
+
|
150
167
|
end
|
151
168
|
|
152
169
|
describe "dimension hierarchies" do
|
@@ -208,6 +225,11 @@ describe "Cube" do
|
|
208
225
|
it "should get hierarchy empty annotations" do
|
209
226
|
@cube.dimension('Gender').hierarchy.annotations.should == {}
|
210
227
|
end
|
228
|
+
|
229
|
+
it "should be visible" do
|
230
|
+
@cube.dimension('Gender').hierarchies.first.should be_visible
|
231
|
+
end
|
232
|
+
|
211
233
|
end
|
212
234
|
|
213
235
|
describe "hierarchy values" do
|
@@ -288,6 +310,10 @@ describe "Cube" do
|
|
288
310
|
@cube.dimension('Gender').hierarchy.level('Gender').annotations.should == {}
|
289
311
|
end
|
290
312
|
|
313
|
+
it "should be visible" do
|
314
|
+
@cube.dimension('Gender').hierarchy.level('Gender').should be_visible
|
315
|
+
end
|
316
|
+
|
291
317
|
end
|
292
318
|
|
293
319
|
describe "members" do
|
@@ -356,6 +382,10 @@ describe "Cube" do
|
|
356
382
|
@cube.member('[Customers].[Non-USA]').should be_calculated
|
357
383
|
end
|
358
384
|
|
385
|
+
it "should be calculated when member is calculated in non-default hierarchy" do
|
386
|
+
@cube.member('[Time.Weekly].[Last week]').should be_calculated
|
387
|
+
end
|
388
|
+
|
359
389
|
it "should not be calculated in query when calculated member defined in schema" do
|
360
390
|
@cube.member('[Customers].[Non-USA]').should_not be_calculated_in_query
|
361
391
|
end
|
@@ -384,11 +414,11 @@ describe "Cube" do
|
|
384
414
|
@cube.member('[Time].[2011]').dimension_type.should == :time
|
385
415
|
end
|
386
416
|
|
387
|
-
it "should be
|
417
|
+
it "should be visible when member is visible" do
|
388
418
|
@cube.member('[Measures].[Store Sales]').should be_visible
|
389
419
|
end
|
390
420
|
|
391
|
-
it "should not be
|
421
|
+
it "should not be visible when member is not visible" do
|
392
422
|
@cube.member('[Measures].[Store Cost]').should_not be_visible
|
393
423
|
end
|
394
424
|
|
data/spec/query_spec.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe "Query" do
|
4
|
-
def
|
5
|
-
ActiveRecord::Base.connection.quote_table_name(name)
|
4
|
+
def qt(name)
|
5
|
+
ActiveRecord::Base.connection.quote_table_name(name.to_s)
|
6
6
|
end
|
7
7
|
|
8
8
|
before(:all) do
|
@@ -21,9 +21,9 @@ describe "Query" do
|
|
21
21
|
FROM sales
|
22
22
|
LEFT JOIN products ON sales.product_id = products.id
|
23
23
|
LEFT JOIN product_classes ON products.product_class_id = product_classes.id
|
24
|
-
LEFT JOIN #{
|
24
|
+
LEFT JOIN #{qt :time} ON sales.time_id = #{qt :time}.id
|
25
25
|
LEFT JOIN customers ON sales.customer_id = customers.id
|
26
|
-
WHERE #{
|
26
|
+
WHERE #{qt :time}.the_year = 2010 AND #{qt :time}.quarter = 'Q1'
|
27
27
|
AND customers.country = 'USA' AND customers.state_province = 'CA'
|
28
28
|
GROUP BY product_classes.product_family
|
29
29
|
ORDER BY product_classes.product_family
|
@@ -282,6 +282,18 @@ describe "Query" do
|
|
282
282
|
end
|
283
283
|
end
|
284
284
|
|
285
|
+
describe "generate" do
|
286
|
+
it "should generate new set" do
|
287
|
+
@query.rows('[Customers].[Country].Members').generate('[Customers].CurrentMember')
|
288
|
+
@query.rows.should == [:generate, ['[Customers].[Country].Members'], ['[Customers].CurrentMember']]
|
289
|
+
end
|
290
|
+
|
291
|
+
it "should generate new set with all option" do
|
292
|
+
@query.rows('[Customers].[Country].Members').generate('[Customers].CurrentMember', :all)
|
293
|
+
@query.rows.should == [:generate, ['[Customers].[Country].Members'], ['[Customers].CurrentMember'], 'ALL']
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
285
297
|
describe "where" do
|
286
298
|
it "should accept conditions" do
|
287
299
|
@query.where('[Time].[2010].[Q1]', '[Customers].[USA].[CA]').should equal(@query)
|
@@ -605,6 +617,26 @@ describe "Query" do
|
|
605
617
|
SQL
|
606
618
|
end
|
607
619
|
|
620
|
+
it "should return query with generate" do
|
621
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
622
|
+
rows('[Customers].[Country].Members').generate('[Customers].CurrentMember').
|
623
|
+
to_mdx.should be_like <<-SQL
|
624
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
625
|
+
GENERATE([Customers].[Country].Members, [Customers].CurrentMember) ON ROWS
|
626
|
+
FROM [Sales]
|
627
|
+
SQL
|
628
|
+
end
|
629
|
+
|
630
|
+
it "should return query with generate all" do
|
631
|
+
@query.columns('[Measures].[Unit Sales]', '[Measures].[Store Sales]').
|
632
|
+
rows('[Customers].[Country].Members').generate('[Customers].CurrentMember', :all).
|
633
|
+
to_mdx.should be_like <<-SQL
|
634
|
+
SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,
|
635
|
+
GENERATE([Customers].[Country].Members, [Customers].CurrentMember, ALL) ON ROWS
|
636
|
+
FROM [Sales]
|
637
|
+
SQL
|
638
|
+
end
|
639
|
+
|
608
640
|
it "should return query including WITH MEMBER clause" do
|
609
641
|
@query.
|
610
642
|
with_member('[Measures].[ProfitPct]').
|
@@ -895,4 +927,50 @@ describe "Query" do
|
|
895
927
|
|
896
928
|
end
|
897
929
|
|
930
|
+
describe "schema cache" do
|
931
|
+
before(:all) do
|
932
|
+
product_id = @sql.select_value("SELECT MIN(id) FROM products")
|
933
|
+
time_id = @sql.select_value("SELECT MIN(id) FROM #{qt :time}")
|
934
|
+
customer_id = @sql.select_value("SELECT MIN(id) FROM customers")
|
935
|
+
@condition = "product_id = #{product_id} AND time_id = #{time_id} AND customer_id = #{customer_id}"
|
936
|
+
# check expected initial value
|
937
|
+
@first_unit_sales = 1
|
938
|
+
@sql.select_value("SELECT unit_sales FROM sales WHERE #{@condition}").to_i.should == @first_unit_sales
|
939
|
+
end
|
940
|
+
|
941
|
+
after(:all) do
|
942
|
+
update_first_unit_sales(@first_unit_sales)
|
943
|
+
end
|
944
|
+
|
945
|
+
def create_olap_connection
|
946
|
+
@olap2.close if @olap2
|
947
|
+
@olap2 = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS_WITH_CATALOG)
|
948
|
+
end
|
949
|
+
|
950
|
+
def update_first_unit_sales(value)
|
951
|
+
@sql.update "UPDATE sales SET unit_sales = #{value} WHERE #{@condition}"
|
952
|
+
end
|
953
|
+
|
954
|
+
def query_unit_sales_value
|
955
|
+
@olap2.from('Sales').columns('[Measures].[Unit Sales]').execute.values.first
|
956
|
+
end
|
957
|
+
|
958
|
+
it "should flush schema cache" do
|
959
|
+
create_olap_connection
|
960
|
+
unit_sales = query_unit_sales_value
|
961
|
+
|
962
|
+
update_first_unit_sales(@first_unit_sales + 1)
|
963
|
+
|
964
|
+
# should still use previous value from cache
|
965
|
+
create_olap_connection
|
966
|
+
query_unit_sales_value.should == unit_sales
|
967
|
+
|
968
|
+
# should query new value from the database after flush schema cache
|
969
|
+
@olap2.flush_schema_cache
|
970
|
+
create_olap_connection
|
971
|
+
query_unit_sales_value.should == unit_sales + 1
|
972
|
+
end
|
973
|
+
|
974
|
+
end
|
975
|
+
|
898
976
|
end
|
data/spec/rake_tasks.rb
CHANGED
@@ -242,7 +242,7 @@ namespace :db do
|
|
242
242
|
end
|
243
243
|
|
244
244
|
namespace :spec do
|
245
|
-
%w(mysql postgresql oracle luciddb mssql sqlserver).each do |driver|
|
245
|
+
%w(mysql jdbc_mysql postgresql oracle luciddb mssql sqlserver).each do |driver|
|
246
246
|
desc "Run specs with #{driver} driver"
|
247
247
|
task driver do
|
248
248
|
ENV['MONDRIAN_DRIVER'] = driver
|
@@ -253,7 +253,7 @@ namespace :spec do
|
|
253
253
|
|
254
254
|
desc "Run specs with all primary database drivers"
|
255
255
|
task :all do
|
256
|
-
%w(mysql postgresql oracle mssql).each do |driver|
|
256
|
+
%w(mysql jdbc_mysql postgresql oracle mssql).each do |driver|
|
257
257
|
Rake::Task["spec:#{driver}"].invoke
|
258
258
|
end
|
259
259
|
end
|
@@ -549,6 +549,27 @@ describe "Schema definition" do
|
|
549
549
|
XML
|
550
550
|
end
|
551
551
|
|
552
|
+
it "should render XML with dimension and hierarchy" do
|
553
|
+
@schema.define do
|
554
|
+
cube 'Sales' do
|
555
|
+
calculated_member 'Current week' do
|
556
|
+
hierarchy '[Time.Weekly]'
|
557
|
+
formula '[Time.Weekly].[Week].CurrentDateMember'
|
558
|
+
end
|
559
|
+
end
|
560
|
+
end
|
561
|
+
@schema.to_xml.should be_like <<-XML
|
562
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
563
|
+
<Schema name="default">
|
564
|
+
<Cube name="Sales">
|
565
|
+
<CalculatedMember hierarchy="[Time.Weekly]" name="Current week">
|
566
|
+
<Formula>[Time.Weekly].[Week].CurrentDateMember</Formula>
|
567
|
+
</CalculatedMember>
|
568
|
+
</Cube>
|
569
|
+
</Schema>
|
570
|
+
XML
|
571
|
+
end
|
572
|
+
|
552
573
|
it "should render embedded cube XML defintion before additional calculated member to XML" do
|
553
574
|
@schema.define do
|
554
575
|
cube 'Sales' do
|
@@ -824,6 +845,8 @@ describe "Schema definition" do
|
|
824
845
|
end
|
825
846
|
|
826
847
|
describe "User defined functions and formatters in JavaScript" do
|
848
|
+
next pending "not supported by Mondrian in Java 8" if ENV_JAVA["java.version"] >= "1.8"
|
849
|
+
|
827
850
|
before(:each) do
|
828
851
|
@schema.define do
|
829
852
|
cube 'Sales' do
|
@@ -957,6 +980,8 @@ describe "Schema definition" do
|
|
957
980
|
end
|
958
981
|
|
959
982
|
describe "User defined functions and formatters in CoffeeScript" do
|
983
|
+
next pending "not supported by Mondrian in Java 8" if ENV_JAVA["java.version"] >= "1.8"
|
984
|
+
|
960
985
|
before(:each) do
|
961
986
|
@schema.define do
|
962
987
|
cube 'Sales' do
|
@@ -1373,6 +1398,136 @@ describe "Schema definition" do
|
|
1373
1398
|
end
|
1374
1399
|
end
|
1375
1400
|
|
1401
|
+
describe "Parameters" do
|
1402
|
+
before(:each) do
|
1403
|
+
@schema.define do
|
1404
|
+
parameter 'Current User', :type => 'String', :modifiable => true, :default_value => "'demo'"
|
1405
|
+
parameter 'Current User 1', :type => 'String', :modifiable => true, :default_value => "''"
|
1406
|
+
parameter 'Default User', :type => 'String', :modifiable => false, :default_value => "'default'"
|
1407
|
+
cube 'Sales' do
|
1408
|
+
table 'sales'
|
1409
|
+
measure 'Unit Sales', :column => 'unit_sales', :aggregator => 'sum'
|
1410
|
+
end
|
1411
|
+
user_defined_function 'ParameterValue' do
|
1412
|
+
ruby :shared do
|
1413
|
+
parameters :string
|
1414
|
+
returns :scalar
|
1415
|
+
syntax :function
|
1416
|
+
def call_with_evaluator(evaluator, parameter_name)
|
1417
|
+
parameter = evaluator.getQuery.getSchemaReader(false).getParameter(parameter_name)
|
1418
|
+
parameter && parameter.getValue
|
1419
|
+
end
|
1420
|
+
end
|
1421
|
+
end
|
1422
|
+
end
|
1423
|
+
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
|
1424
|
+
end
|
1425
|
+
|
1426
|
+
it "should render XML" do
|
1427
|
+
@schema.to_xml.should be_like <<-XML
|
1428
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
1429
|
+
<Schema name="default">
|
1430
|
+
<Parameter defaultValue="'demo'" modifiable="true" name="Current User" type="String"/>
|
1431
|
+
<Parameter defaultValue="''" modifiable="true" name="Current User 1" type="String"/>
|
1432
|
+
<Parameter defaultValue="'default'" modifiable="false" name="Default User" type="String"/>
|
1433
|
+
<Cube name="Sales">
|
1434
|
+
<Table name="sales"/>
|
1435
|
+
<Measure aggregator="sum" column="unit_sales" name="Unit Sales"/>
|
1436
|
+
</Cube>
|
1437
|
+
<UserDefinedFunction className="rubyobj.Mondrian.OLAP.Schema.UserDefinedFunction.ParametervalueUdf" name="ParameterValue"/>
|
1438
|
+
</Schema>
|
1439
|
+
XML
|
1440
|
+
end
|
1441
|
+
|
1442
|
+
it "should get parameter definition from connection" do
|
1443
|
+
parameter = @olap.mondrian_parameter('Current User')
|
1444
|
+
parameter.should_not be_nil
|
1445
|
+
parameter.name.should == 'Current User'
|
1446
|
+
parameter.description.should be_nil
|
1447
|
+
parameter.should be_modifiable
|
1448
|
+
parameter.scope.to_s.should == 'Schema'
|
1449
|
+
parameter.type.to_s.should == 'STRING'
|
1450
|
+
end
|
1451
|
+
|
1452
|
+
it "should not get parameter definition with invalid name" do
|
1453
|
+
@olap.mondrian_parameter('Current User 2').should be_nil
|
1454
|
+
end
|
1455
|
+
|
1456
|
+
it "should get default parameter value with ParamRef" do
|
1457
|
+
result = @olap.from('Sales').
|
1458
|
+
with_member('[Measures].[Current User]').as("ParamRef('Current User')").
|
1459
|
+
columns('[Measures].[Current User]').execute
|
1460
|
+
result.values.should == ['demo']
|
1461
|
+
end
|
1462
|
+
|
1463
|
+
it "should execute query with schema parameter value and get value with ParamRef" do
|
1464
|
+
result = @olap.from('Sales').
|
1465
|
+
with_member('[Measures].[Current User]').as("ParamRef('Current User 1')").
|
1466
|
+
columns('[Measures].[Current User]').execute("Current User 1" => "test")
|
1467
|
+
result.values.should == ['test']
|
1468
|
+
end
|
1469
|
+
|
1470
|
+
it "should execute query with query parameter value and get value with Parameter" do
|
1471
|
+
result = @olap.from('Sales').
|
1472
|
+
with_member('[Measures].[Current User]').as("Parameter('Current User 2', String, 'demo2')").
|
1473
|
+
columns('[Measures].[Current User]').execute("Current User 2" => "test2")
|
1474
|
+
result.values.should == ['test2']
|
1475
|
+
end
|
1476
|
+
|
1477
|
+
# can be used in user defined functions
|
1478
|
+
it "should execute query with additional defined parameter string value" do
|
1479
|
+
result = @olap.from('Sales').
|
1480
|
+
with_member('[Measures].[Parameter]').as("ParameterValue('String Parameter')").
|
1481
|
+
columns('[Measures].[Parameter]').execute(:define_parameters => {"String Parameter" => "test"})
|
1482
|
+
result.values.should == ['test']
|
1483
|
+
end
|
1484
|
+
|
1485
|
+
it "should execute query with additional defined parameter integer value" do
|
1486
|
+
result = @olap.from('Sales').
|
1487
|
+
with_member('[Measures].[Parameter]').as("ParameterValue('Integer Parameter')").
|
1488
|
+
columns('[Measures].[Parameter]').execute(:define_parameters => {"Integer Parameter" => 123})
|
1489
|
+
result.values.should == [123]
|
1490
|
+
end
|
1491
|
+
|
1492
|
+
it "should execute query with additional defined parameter double value" do
|
1493
|
+
result = @olap.from('Sales').
|
1494
|
+
with_member('[Measures].[Parameter]').as("ParameterValue('Double Parameter')").
|
1495
|
+
columns('[Measures].[Parameter]').execute(:define_parameters => {"Double Parameter" => 123.456})
|
1496
|
+
result.values.should == [123.456]
|
1497
|
+
end
|
1498
|
+
|
1499
|
+
it "should execute query with additional defined parameter nil value" do
|
1500
|
+
result = @olap.from('Sales').
|
1501
|
+
with_member('[Measures].[Parameter]').as("ParameterValue('Nil Parameter')").
|
1502
|
+
columns('[Measures].[Parameter]').execute(:define_parameters => {"Nil Parameter" => nil})
|
1503
|
+
result.values.should == [nil]
|
1504
|
+
end
|
1505
|
+
|
1506
|
+
it "should fail if executing with invalid parameter name" do
|
1507
|
+
expect {
|
1508
|
+
@olap.from('Sales').
|
1509
|
+
with_member('[Measures].[Current User]').as("'dummy'").
|
1510
|
+
columns('[Measures].[Current User]').execute("Current User 2" => "test2")
|
1511
|
+
}.to raise_error {|e|
|
1512
|
+
e.should be_kind_of(Mondrian::OLAP::Error)
|
1513
|
+
e.message.should == "mondrian.olap.MondrianException: Mondrian Error:Unknown parameter 'Current User 2'"
|
1514
|
+
e.root_cause_message.should == "Unknown parameter 'Current User 2'"
|
1515
|
+
}
|
1516
|
+
end
|
1517
|
+
|
1518
|
+
it "should fail if executing with non-modifiable parameter" do
|
1519
|
+
expect {
|
1520
|
+
@olap.from('Sales').
|
1521
|
+
with_member('[Measures].[Current User]').as("'dummy'").
|
1522
|
+
columns('[Measures].[Current User]').execute("Default User" => "test")
|
1523
|
+
}.to raise_error {|e|
|
1524
|
+
e.should be_kind_of(Mondrian::OLAP::Error)
|
1525
|
+
e.message.should == "mondrian.olap.MondrianException: Mondrian Error:Parameter 'Default User' (defined at 'Schema' scope) is not modifiable"
|
1526
|
+
e.root_cause_message.should == "Parameter 'Default User' (defined at 'Schema' scope) is not modifiable"
|
1527
|
+
}
|
1528
|
+
end
|
1529
|
+
end
|
1530
|
+
|
1376
1531
|
end
|
1377
1532
|
|
1378
1533
|
describe "connection with schema" do
|
data/spec/spec_helper.rb
CHANGED
@@ -19,7 +19,7 @@ DATABASE_NAME = ENV["#{env_prefix}_DATABASE_NAME"] || ENV['DATABASE_NAME
|
|
19
19
|
DATABASE_INSTANCE = ENV["#{env_prefix}_DATABASE_INSTANCE"] || ENV['DATABASE_INSTANCE']
|
20
20
|
|
21
21
|
case MONDRIAN_DRIVER
|
22
|
-
when 'mysql'
|
22
|
+
when 'mysql', 'jdbc_mysql'
|
23
23
|
require 'jdbc/mysql'
|
24
24
|
JDBC_DRIVER = 'com.mysql.jdbc.Driver'
|
25
25
|
when 'postgresql'
|
@@ -75,15 +75,25 @@ end
|
|
75
75
|
|
76
76
|
CATALOG_FILE = File.expand_path('../fixtures/MondrianTest.xml', __FILE__) unless defined?(CATALOG_FILE)
|
77
77
|
|
78
|
-
CONNECTION_PARAMS =
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
78
|
+
CONNECTION_PARAMS = if MONDRIAN_DRIVER =~ /^jdbc/
|
79
|
+
{
|
80
|
+
:driver => 'jdbc',
|
81
|
+
:jdbc_url => "jdbc:#{MONDRIAN_DRIVER.split('_').last}://#{DATABASE_HOST}/#{DATABASE_NAME}",
|
82
|
+
:jdbc_driver => JDBC_DRIVER,
|
83
|
+
:username => DATABASE_USER,
|
84
|
+
:password => DATABASE_PASSWORD
|
85
|
+
}
|
86
|
+
else
|
87
|
+
{
|
88
|
+
# uncomment to test PostgreSQL SSL connection
|
89
|
+
# :properties => {'ssl'=>'true','sslfactory'=>'org.postgresql.ssl.NonValidatingFactory'},
|
90
|
+
:driver => MONDRIAN_DRIVER,
|
91
|
+
:host => DATABASE_HOST,
|
92
|
+
:database => DATABASE_NAME,
|
93
|
+
:username => DATABASE_USER,
|
94
|
+
:password => DATABASE_PASSWORD
|
95
|
+
}
|
96
|
+
end
|
87
97
|
|
88
98
|
case MONDRIAN_DRIVER
|
89
99
|
when 'oracle'
|
@@ -126,6 +136,14 @@ when 'sqlserver'
|
|
126
136
|
:password => CONNECTION_PARAMS[:password],
|
127
137
|
:connection_alive_sql => 'SELECT 1'
|
128
138
|
}
|
139
|
+
when /jdbc/
|
140
|
+
AR_CONNECTION_PARAMS = {
|
141
|
+
:adapter => 'jdbc',
|
142
|
+
:driver => JDBC_DRIVER,
|
143
|
+
:url => CONNECTION_PARAMS[:jdbc_url],
|
144
|
+
:username => CONNECTION_PARAMS[:username],
|
145
|
+
:password => CONNECTION_PARAMS[:password]
|
146
|
+
}
|
129
147
|
else
|
130
148
|
AR_CONNECTION_PARAMS = {
|
131
149
|
# uncomment to test PostgreSQL SSL connection
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mondrian-olap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Raimonds Simanovskis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-11-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -236,20 +236,21 @@ files:
|
|
236
236
|
- VERSION
|
237
237
|
- lib/mondrian-olap.rb
|
238
238
|
- lib/mondrian/olap.rb
|
239
|
-
- lib/mondrian/jars/commons-collections-3.2.jar
|
240
|
-
- lib/mondrian/jars/commons-dbcp-1.
|
239
|
+
- lib/mondrian/jars/commons-collections-3.2.1.jar
|
240
|
+
- lib/mondrian/jars/commons-dbcp-1.4.jar
|
241
|
+
- lib/mondrian/jars/commons-io-2.2.jar
|
241
242
|
- lib/mondrian/jars/commons-logging-1.1.1.jar
|
242
243
|
- lib/mondrian/jars/commons-math-1.1.jar
|
243
|
-
- lib/mondrian/jars/commons-pool-1.
|
244
|
-
- lib/mondrian/jars/commons-vfs-
|
244
|
+
- lib/mondrian/jars/commons-pool-1.5.7.jar
|
245
|
+
- lib/mondrian/jars/commons-vfs-20100924-pentaho.jar
|
245
246
|
- lib/mondrian/jars/eigenbase-properties-1.1.2.jar
|
246
247
|
- lib/mondrian/jars/eigenbase-resgen-1.3.1.jar
|
247
248
|
- lib/mondrian/jars/eigenbase-xom-1.3.1.jar
|
248
249
|
- lib/mondrian/jars/javacup-10k.jar
|
249
|
-
- lib/mondrian/jars/log4j-1.2.
|
250
|
+
- lib/mondrian/jars/log4j-1.2.17.jar
|
250
251
|
- lib/mondrian/jars/log4j.properties
|
251
|
-
- lib/mondrian/jars/mondrian.jar
|
252
|
-
- lib/mondrian/jars/olap4j-1.0.
|
252
|
+
- lib/mondrian/jars/mondrian-3.8.0.0-209.jar
|
253
|
+
- lib/mondrian/jars/olap4j-1.2.0.jar
|
253
254
|
- lib/mondrian/olap/connection.rb
|
254
255
|
- lib/mondrian/olap/cube.rb
|
255
256
|
- lib/mondrian/olap/error.rb
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|