mondrian-olap 1.2.0 → 1.3.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 +21 -1
- data/README.md +4 -4
- data/VERSION +1 -1
- data/lib/mondrian/jars/log4j-api-2.17.1.jar +0 -0
- data/lib/mondrian/jars/log4j-core-2.17.1.jar +0 -0
- data/lib/mondrian/jars/log4j2-config.jar +0 -0
- data/lib/mondrian/jars/mondrian-9.3.0.0.jar +0 -0
- data/lib/mondrian/olap/connection.rb +33 -20
- data/lib/mondrian/olap/cube.rb +46 -4
- data/lib/mondrian/olap/error.rb +10 -2
- data/lib/mondrian/olap/query.rb +1 -0
- data/lib/mondrian/olap/result.rb +128 -59
- data/lib/mondrian/olap/schema.rb +9 -3
- data/lib/mondrian/olap/schema_udf.rb +8 -82
- data/lib/mondrian/olap.rb +11 -7
- data/spec/connection_spec.rb +37 -3
- data/spec/cube_cache_control_spec.rb +2 -2
- data/spec/cube_spec.rb +36 -2
- data/spec/fixtures/MondrianTest.xml +40 -0
- data/spec/fixtures/MondrianTestOracle.xml +40 -0
- data/spec/mondrian_spec.rb +132 -0
- data/spec/query_spec.rb +86 -21
- data/spec/rake_tasks.rb +85 -16
- data/spec/schema_definition_spec.rb +0 -235
- data/spec/spec_helper.rb +317 -75
- data/spec/support/data/customers.csv +111 -111
- data/spec/support/data/promotions.csv +11 -0
- data/spec/support/data/sales.csv +101 -101
- data/spec/support/data/warehouse.csv +101 -0
- data/spec/support/matchers/be_like.rb +1 -0
- metadata +36 -73
- data/lib/mondrian/jars/log4j-1.2.17.jar +0 -0
- data/lib/mondrian/jars/log4j.properties +0 -3
- data/lib/mondrian/jars/mondrian-9.1.0.0.jar +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f016aae4b1d6d9f4138ae3bded251944cf7fcfe8f1bd0579df7f962eb1a52d03
|
4
|
+
data.tar.gz: 8b5a4d5c4526f2fd7a239c3dccd57c7d08f5f1d842354fae5bd13f21920e7e73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a9b3633a62cef270a163c25c5f1511442938b6705b2385fb2b7e2c2a8f9a1a2041b3f041af0be995970fa86307c4fe1af3c0fb94488c86cc1ec617b93acfe3b
|
7
|
+
data.tar.gz: 82ee663a5017af81ed6089f6f162e6d8a98458c1a7afb50e5bf5c3b27ea7422b91e81cc38e099a0a23d7ae99368b8c425083c55b6a6d1f1b75a04566cc664b0a
|
data/Changelog.md
CHANGED
@@ -1,4 +1,24 @@
|
|
1
|
-
###
|
1
|
+
### 1.3.0 / 2023-06-02
|
2
|
+
|
3
|
+
* New features
|
4
|
+
* Upgrade to the latest Mondrian version 9.3.0.0 with additional patches
|
5
|
+
* Additional Mondrian patches and improvements
|
6
|
+
* Support for ClickHouse database
|
7
|
+
* Support for MariaDB ColumnStore engine
|
8
|
+
* Improve Aggregate performance of large compound slicers when mondrian.rolap.EnableInMemoryRollup=false
|
9
|
+
* Improve performance of Mondrian member property value lookup
|
10
|
+
* Set dynamic Mondrian connection pool size based on mondrian.rolap.maxSqlThreads property
|
11
|
+
* Skip registration of MondrianOlap4jDriver if mondrian.olap4j.registerDriver=false
|
12
|
+
* Enable Mondrian supportsMultiValueInExpr for PostgreSQL and Oracle
|
13
|
+
* Upgrade to log4j2
|
14
|
+
* Support instance parameter for SQL Server connection
|
15
|
+
* Allow to specify high_cardinality for dimension
|
16
|
+
* Remove deprecated jTDS driver support, use MS JDBC driver instead
|
17
|
+
* Remove user defined functions and formatters in JavaScript and CoffeeScript as they are not supported since JVM 8
|
18
|
+
* Upgrade tests to use the later ActiveRecord and RSpec versions
|
19
|
+
* Tested with JRuby 9.4 and JVM 17
|
20
|
+
* Bug fixes
|
21
|
+
* Patch for MONDRIAN-2714 (fixed support for MySQL JDBC driver version 8.0.23)
|
2
22
|
|
3
23
|
### 1.2.0 / 2021-03-06
|
4
24
|
|
data/README.md
CHANGED
@@ -215,8 +215,8 @@ See more examples of dimension and member queries in `spec/cube_spec.rb`.
|
|
215
215
|
|
216
216
|
### User defined MDX functions
|
217
217
|
|
218
|
-
You can define new MDX functions using
|
219
|
-
|
218
|
+
You can define new MDX functions using Ruby that you can later use either in calculated member formulas or in MDX queries.
|
219
|
+
Here are examples of user defined functions in Ruby:
|
220
220
|
|
221
221
|
```ruby
|
222
222
|
schema = Mondrian::OLAP::Schema.define do
|
@@ -273,9 +273,9 @@ See more examples of data access roles in `spec/connection_role_spec.rb`.
|
|
273
273
|
REQUIREMENTS
|
274
274
|
------------
|
275
275
|
|
276
|
-
mondrian-olap gem is compatible with JRuby
|
276
|
+
mondrian-olap gem is compatible with JRuby versions 9.3.x and 9.4.x, JVM 8, 11, and 17. mondrian-olap works only with JRuby and not with other Ruby implementations as it includes Mondrian OLAP Java libraries.
|
277
277
|
|
278
|
-
mondrian-olap supports MySQL, PostgreSQL, Oracle, Microsoft SQL Server, Vertica and
|
278
|
+
mondrian-olap supports MySQL, PostgreSQL, Oracle, Microsoft SQL Server, Vertica, Snowflake, and ClickHouse databases as well as other databases that are supported by Mondrian OLAP engine (using jdbc_driver and jdbc_url connection parameters). When using MySQL or PostgreSQL databases then install jdbc-mysql or jdbc-postgres gem and require "jdbc/mysql" or "jdbc/postgres" to load the corresponding JDBC database driver. When using Oracle then require Oracle JDBC driver `ojdbc*.jar`. When using MS SQL Server you then use the Microsoft JDBC driver `mssql-jdbc-*.jar`. When using Vertica, Snowflake, or ClickHouse then require corresponding JDBC drivers.
|
279
279
|
|
280
280
|
INSTALL
|
281
281
|
-------
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.3.0
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -289,6 +289,14 @@ module Mondrian
|
|
289
289
|
true
|
290
290
|
end
|
291
291
|
|
292
|
+
def jdbc_uri
|
293
|
+
if respond_to?(method_name = "jdbc_uri_#{@driver}", true)
|
294
|
+
send method_name
|
295
|
+
else
|
296
|
+
raise ArgumentError, 'unknown JDBC driver'
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
292
300
|
private
|
293
301
|
|
294
302
|
def connection_string
|
@@ -306,17 +314,10 @@ module Mondrian
|
|
306
314
|
string + (@params[:catalog] ? "Catalog=#{catalog_uri}" : "CatalogContent=#{quote_string(catalog_content)}")
|
307
315
|
end
|
308
316
|
|
309
|
-
def jdbc_uri
|
310
|
-
if respond_to?(method_name = "jdbc_uri_#{@driver}", true)
|
311
|
-
send method_name
|
312
|
-
else
|
313
|
-
raise ArgumentError, 'unknown JDBC driver'
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
317
|
def jdbc_uri_generic(options = {})
|
318
318
|
uri_prefix = options[:uri_prefix] || "jdbc:#{@driver}://"
|
319
|
-
|
319
|
+
port = @params[:port] || options[:default_port]
|
320
|
+
uri = "#{uri_prefix}#{@params[:host]}#{port && ":#{port}"}"
|
320
321
|
uri += "/#{@params[:database]}" if @params[:database] && options[:add_database] != false
|
321
322
|
properties = new_empty_properties
|
322
323
|
properties.merge!(options[:default_properties]) if options[:default_properties].is_a?(Hash)
|
@@ -343,6 +344,7 @@ module Mondrian
|
|
343
344
|
|
344
345
|
alias_method :jdbc_uri_postgresql, :jdbc_uri_generic
|
345
346
|
alias_method :jdbc_uri_vertica, :jdbc_uri_generic
|
347
|
+
alias_method :jdbc_uri_mariadb, :jdbc_uri_generic
|
346
348
|
|
347
349
|
def jdbc_uri_oracle
|
348
350
|
# connection using TNS alias
|
@@ -360,18 +362,12 @@ module Mondrian
|
|
360
362
|
end
|
361
363
|
end
|
362
364
|
|
363
|
-
def jdbc_uri_mssql
|
364
|
-
jdbc_uri_generic(
|
365
|
-
uri_prefix: 'jdbc:jtds:sqlserver://', separator: ';', first_separator: ';',
|
366
|
-
default_properties: @params.slice(:instance, :domain, :appname)
|
367
|
-
)
|
368
|
-
end
|
369
|
-
|
370
365
|
JDBC_SQLSERVER_PARAM_PROPERTIES = {
|
371
366
|
database: 'databaseName',
|
372
367
|
integrated_security: 'integratedSecurity',
|
373
368
|
application_name: 'applicationName',
|
374
|
-
instance_name: 'instanceName'
|
369
|
+
instance_name: 'instanceName',
|
370
|
+
instance: 'instanceName'
|
375
371
|
}
|
376
372
|
|
377
373
|
def jdbc_uri_sqlserver
|
@@ -404,6 +400,15 @@ module Mondrian
|
|
404
400
|
)
|
405
401
|
end
|
406
402
|
|
403
|
+
def jdbc_uri_clickhouse
|
404
|
+
protocol_prefix = if protocol = @params[:protocol]
|
405
|
+
raise ArgumentError, "invalid protocol #{protocol}" unless protocol =~ /\A\w+\z/
|
406
|
+
":#{protocol}"
|
407
|
+
end
|
408
|
+
uri_prefix = "jdbc:ch#{protocol_prefix}://"
|
409
|
+
jdbc_uri_generic(uri_prefix: uri_prefix)
|
410
|
+
end
|
411
|
+
|
407
412
|
def jdbc_uri_jdbc
|
408
413
|
@params[:jdbc_url] or raise ArgumentError, 'missing jdbc_url parameter'
|
409
414
|
end
|
@@ -411,10 +416,11 @@ module Mondrian
|
|
411
416
|
JDBC_DRIVER_CLASS = {
|
412
417
|
'postgresql' => 'org.postgresql.Driver',
|
413
418
|
'oracle' => 'oracle.jdbc.OracleDriver',
|
414
|
-
'mssql' => 'net.sourceforge.jtds.jdbc.Driver',
|
415
419
|
'sqlserver' => 'com.microsoft.sqlserver.jdbc.SQLServerDriver',
|
416
420
|
'vertica' => 'com.vertica.jdbc.Driver',
|
417
|
-
'snowflake' => 'net.snowflake.client.jdbc.SnowflakeDriver'
|
421
|
+
'snowflake' => 'net.snowflake.client.jdbc.SnowflakeDriver',
|
422
|
+
'clickhouse' => 'com.clickhouse.jdbc.ClickHouseDriver',
|
423
|
+
'mariadb' => 'org.mariadb.jdbc.Driver'
|
418
424
|
}
|
419
425
|
|
420
426
|
def jdbc_driver
|
@@ -474,6 +480,9 @@ module Mondrian
|
|
474
480
|
end
|
475
481
|
end
|
476
482
|
|
483
|
+
# Starting from Mondrian 9.2 additional QueryBody plan string is added at the end which will be ignored.
|
484
|
+
QUERY_BODY_PLAN_REGEXP = /\AQueryBody:/
|
485
|
+
|
477
486
|
class ProfilingHandler
|
478
487
|
java_implements Java::mondrian.spi.ProfileHandler
|
479
488
|
attr_reader :plan
|
@@ -481,7 +490,11 @@ module Mondrian
|
|
481
490
|
|
482
491
|
java_signature 'void explain(String plan, mondrian.olap.QueryTiming timing)'
|
483
492
|
def explain(plan, timing)
|
484
|
-
@plan
|
493
|
+
if @plan
|
494
|
+
@plan += "\n" + plan unless plan =~ QUERY_BODY_PLAN_REGEXP
|
495
|
+
else
|
496
|
+
@plan = plan
|
497
|
+
end
|
485
498
|
@timing = timing
|
486
499
|
end
|
487
500
|
end
|
data/lib/mondrian/olap/cube.rb
CHANGED
@@ -56,15 +56,35 @@ module Mondrian
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def dimensions
|
59
|
-
@dimenstions ||= @raw_cube.getDimensions.map{|d|
|
59
|
+
@dimenstions ||= @raw_cube.getDimensions.map { |d| dimension_from_raw(d) }
|
60
60
|
end
|
61
61
|
|
62
62
|
def dimension_names
|
63
|
-
dimensions.map
|
63
|
+
dimensions.map(&:name)
|
64
64
|
end
|
65
65
|
|
66
66
|
def dimension(name)
|
67
|
-
dimensions
|
67
|
+
if @dimensions
|
68
|
+
@dimensions.detect { |d| d.name == name }
|
69
|
+
elsif raw_dimension = @raw_cube.getDimensions.detect { |d| d.getName == name }
|
70
|
+
dimension_from_raw(raw_dimension)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def hierarchies
|
75
|
+
@hierarchies ||= @raw_cube.getHierarchies.map { |h| hierarchy_from_raw(h) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def hierarchy_names
|
79
|
+
hierarchies.map(&:name)
|
80
|
+
end
|
81
|
+
|
82
|
+
def hierarchy(name)
|
83
|
+
if @hierarchies
|
84
|
+
@hierarchies.detect { |h| h.name == name }
|
85
|
+
elsif raw_hierarchy = @raw_cube.getHierarchies.detect { |h| h.getName == name }
|
86
|
+
hierarchy_from_raw(raw_hierarchy)
|
87
|
+
end
|
68
88
|
end
|
69
89
|
|
70
90
|
def query
|
@@ -85,6 +105,16 @@ module Mondrian
|
|
85
105
|
|
86
106
|
def_delegators :@cache_control, :flush_region_cache_with_segments, :flush_region_cache_with_segments
|
87
107
|
def_delegators :@cache_control, :flush_region_cache_with_full_names, :flush_region_cache_with_full_names
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def dimension_from_raw(raw_dimension)
|
112
|
+
Dimension.new(self, raw_dimension)
|
113
|
+
end
|
114
|
+
|
115
|
+
def hierarchy_from_raw(raw_hierarchy)
|
116
|
+
Hierarchy.new(dimension_from_raw(raw_hierarchy.getDimension), raw_hierarchy)
|
117
|
+
end
|
88
118
|
end
|
89
119
|
|
90
120
|
class Dimension
|
@@ -156,7 +186,7 @@ module Mondrian
|
|
156
186
|
@raw_hierarchy = raw_hierarchy
|
157
187
|
end
|
158
188
|
|
159
|
-
attr_reader :raw_hierarchy
|
189
|
+
attr_reader :raw_hierarchy, :dimension
|
160
190
|
|
161
191
|
def name
|
162
192
|
@name ||= @raw_hierarchy.getName
|
@@ -170,6 +200,10 @@ module Mondrian
|
|
170
200
|
@caption ||= @raw_hierarchy.getCaption
|
171
201
|
end
|
172
202
|
|
203
|
+
def dimension_name
|
204
|
+
@dimension.name
|
205
|
+
end
|
206
|
+
|
173
207
|
def levels
|
174
208
|
@levels = @raw_hierarchy.getLevels.map{|l| Level.new(self, l)}
|
175
209
|
end
|
@@ -257,6 +291,10 @@ module Mondrian
|
|
257
291
|
@cardinality = @raw_level.getCardinality
|
258
292
|
end
|
259
293
|
|
294
|
+
def cardinality=(value)
|
295
|
+
mondrian_level.setApproxRowCount(value || Java::JavaLang::Integer::MIN_VALUE)
|
296
|
+
end
|
297
|
+
|
260
298
|
def members_count
|
261
299
|
@members_count ||= begin
|
262
300
|
if cardinality >= 0
|
@@ -275,6 +313,10 @@ module Mondrian
|
|
275
313
|
end
|
276
314
|
end
|
277
315
|
|
316
|
+
def mondrian_level
|
317
|
+
@raw_level.unwrap(Java::MondrianOlap::Level.java_class)
|
318
|
+
end
|
319
|
+
|
278
320
|
include Annotated
|
279
321
|
def annotations
|
280
322
|
annotations_for(@raw_level)
|
data/lib/mondrian/olap/error.rb
CHANGED
@@ -39,7 +39,7 @@ module Mondrian
|
|
39
39
|
|
40
40
|
def profiling_timing_string
|
41
41
|
if profiling_timing && (timing_string = profiling_timing.toString)
|
42
|
-
timing_string.gsub("\r\n", "\n")
|
42
|
+
timing_string.gsub("\r\n", "\n").sub(Mondrian::OLAP::Result::QUERY_TIMING_CUMULATIVE_REGEXP, '')
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
@@ -76,7 +76,15 @@ module Mondrian
|
|
76
76
|
f.accessible = true
|
77
77
|
if cell_set = f.value(statement)
|
78
78
|
cell_set.close
|
79
|
-
|
79
|
+
# Starting from Mondrian 9.2 query plan was not available in case of error, need to get it explicitly.
|
80
|
+
if (@profiling_handler = statement.getProfileHandler) && !@profiling_handler.timing
|
81
|
+
query = statement.getQuery
|
82
|
+
string_writer = Java::java.io.StringWriter.new
|
83
|
+
print_writer = Java::java.io.PrintWriter.new(string_writer)
|
84
|
+
query.explain(print_writer)
|
85
|
+
print_writer.close
|
86
|
+
@profiling_handler.explain(string_writer.toString, cell_set.getQueryTiming)
|
87
|
+
end
|
80
88
|
end
|
81
89
|
end
|
82
90
|
end
|
data/lib/mondrian/olap/query.rb
CHANGED
@@ -296,6 +296,7 @@ module Mondrian
|
|
296
296
|
}
|
297
297
|
|
298
298
|
def members_to_mdx(members)
|
299
|
+
members ||= []
|
299
300
|
# if only one member which does not end with ] or .Item(...)
|
300
301
|
# then assume it is expression which returns set
|
301
302
|
# TODO: maybe always include also single expressions in {...} to avoid some edge cases?
|
data/lib/mondrian/olap/result.rb
CHANGED
@@ -127,9 +127,11 @@ module Mondrian
|
|
127
127
|
profiling_timing && profiling_timing.markFull(name, duration)
|
128
128
|
end
|
129
129
|
|
130
|
+
QUERY_TIMING_CUMULATIVE_REGEXP = /\AQuery Timing \(Cumulative\):\n/
|
131
|
+
|
130
132
|
def profiling_timing_string
|
131
133
|
if profiling_timing && (timing_string = profiling_timing.toString)
|
132
|
-
timing_string.gsub("\r\n", "\n")
|
134
|
+
timing_string.gsub("\r\n", "\n").sub(QUERY_TIMING_CUMULATIVE_REGEXP, '')
|
133
135
|
end
|
134
136
|
end
|
135
137
|
|
@@ -147,13 +149,12 @@ module Mondrian
|
|
147
149
|
cell_params << Java::JavaLang::Integer.new(axis_position)
|
148
150
|
end
|
149
151
|
raw_cell = @raw_cell_set.getCell(cell_params)
|
150
|
-
DrillThrough.from_raw_cell(raw_cell, params)
|
152
|
+
DrillThrough.from_raw_cell(raw_cell, params.merge(role_name: @connection.role_name))
|
151
153
|
end
|
152
154
|
end
|
153
155
|
|
154
156
|
class DrillThrough
|
155
157
|
def self.from_raw_cell(raw_cell, params = {})
|
156
|
-
max_rows = params[:max_rows] || -1
|
157
158
|
# workaround to avoid calling raw_cell.drillThroughInternal private method
|
158
159
|
# which fails when running inside TorqueBox
|
159
160
|
cell_field = raw_cell.java_class.declared_field('cell')
|
@@ -161,14 +162,18 @@ module Mondrian
|
|
161
162
|
rolap_cell = cell_field.value(raw_cell)
|
162
163
|
|
163
164
|
if params[:return] || rolap_cell.canDrillThrough
|
164
|
-
sql_statement = drill_through_internal(rolap_cell, params)
|
165
|
+
sql_statement, return_fields = drill_through_internal(rolap_cell, params)
|
165
166
|
raw_result_set = sql_statement.getWrappedResultSet
|
166
|
-
|
167
|
+
raw_cube = raw_cell.getCellSet.getMetaData.getCube
|
168
|
+
new(raw_result_set, return_fields: return_fields, raw_cube: raw_cube, role_name: params[:role_name])
|
167
169
|
end
|
168
170
|
end
|
169
171
|
|
170
|
-
def initialize(raw_result_set)
|
172
|
+
def initialize(raw_result_set, options = {})
|
171
173
|
@raw_result_set = raw_result_set
|
174
|
+
@return_fields = options[:return_fields]
|
175
|
+
@raw_cube = options[:raw_cube]
|
176
|
+
@role_name = options[:role_name]
|
172
177
|
end
|
173
178
|
|
174
179
|
def column_types
|
@@ -207,7 +212,7 @@ module Mondrian
|
|
207
212
|
column_types.each_with_index do |column_type, i|
|
208
213
|
row_values << Result.java_to_ruby_value(@raw_result_set.getObject(i + 1), column_type)
|
209
214
|
end
|
210
|
-
row_values
|
215
|
+
can_access_row_values?(row_values) ? row_values : fetch
|
211
216
|
else
|
212
217
|
@raw_result_set.close
|
213
218
|
nil
|
@@ -226,6 +231,35 @@ module Mondrian
|
|
226
231
|
|
227
232
|
private
|
228
233
|
|
234
|
+
def can_access_row_values?(row_values)
|
235
|
+
return true unless @role_name
|
236
|
+
|
237
|
+
member_full_name_columns_indexes.each do |column_indexes|
|
238
|
+
segment_names = [@return_fields[column_indexes.first][:member].getHierarchy.getName]
|
239
|
+
column_indexes.each { |i| segment_names << row_values[i].to_s }
|
240
|
+
segment_list = Java::OrgOlap4jMdx::IdentifierNode.ofNames(*segment_names).getSegmentList
|
241
|
+
return false unless @raw_cube.lookupMember(segment_list)
|
242
|
+
end
|
243
|
+
|
244
|
+
true
|
245
|
+
end
|
246
|
+
|
247
|
+
def member_full_name_columns_indexes
|
248
|
+
@member_full_name_columns_indexes ||= begin
|
249
|
+
fieldset_columns = Hash.new { |h, k| h[k] = Array.new }
|
250
|
+
column_labels.each_with_index do |label, i|
|
251
|
+
# Find all role restriction columns with a label pattern "_level:<Fieldset ID>:<Level depth>"
|
252
|
+
if label =~ /\A_level:(\d+):(\d+)\z/
|
253
|
+
# Group by fieldset ID with a compound value of level depth and column index
|
254
|
+
fieldset_columns[$1] << [$2.to_i, i]
|
255
|
+
end
|
256
|
+
end
|
257
|
+
# For each fieldset create an array with columns indexes sorted by level depth
|
258
|
+
fieldset_columns.each { |k, v| fieldset_columns[k] = v.sort_by(&:first).map(&:last) }
|
259
|
+
fieldset_columns.values
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
229
263
|
def metadata
|
230
264
|
@metadata ||= @raw_result_set.getMetaData
|
231
265
|
end
|
@@ -238,7 +272,7 @@ module Mondrian
|
|
238
272
|
result_field.accessible = true
|
239
273
|
result = result_field.value(rolap_cell)
|
240
274
|
|
241
|
-
sql = generate_drill_through_sql(rolap_cell, result, params)
|
275
|
+
sql, return_fields = generate_drill_through_sql(rolap_cell, result, params)
|
242
276
|
|
243
277
|
# Choose the appropriate scrollability. If we need to start from an
|
244
278
|
# offset row, it is useful that the cursor is scrollable, but not
|
@@ -248,10 +282,8 @@ module Mondrian
|
|
248
282
|
connection = statement.getMondrianConnection
|
249
283
|
result_set_type = Java::JavaSql::ResultSet::TYPE_FORWARD_ONLY
|
250
284
|
result_set_concurrency = Java::JavaSql::ResultSet::CONCUR_READ_ONLY
|
251
|
-
schema = statement.getSchema
|
252
|
-
dialect = schema.getDialect
|
253
285
|
|
254
|
-
Java::MondrianRolap::RolapUtil.executeQuery(
|
286
|
+
sql_statement = Java::MondrianRolap::RolapUtil.executeQuery(
|
255
287
|
connection.getDataSource,
|
256
288
|
sql,
|
257
289
|
nil,
|
@@ -267,11 +299,12 @@ module Mondrian
|
|
267
299
|
result_set_concurrency,
|
268
300
|
nil
|
269
301
|
)
|
302
|
+
[sql_statement, return_fields]
|
270
303
|
end
|
271
304
|
|
272
305
|
def self.generate_drill_through_sql(rolap_cell, result, params)
|
273
306
|
nonempty_columns, return_fields = parse_return_fields(result, params)
|
274
|
-
return_expressions = return_fields.map{|field| field[:member]}
|
307
|
+
return_expressions = return_fields.map { |field| field[:member] }
|
275
308
|
|
276
309
|
sql_non_extended = rolap_cell.getDrillThroughSQL(return_expressions, false)
|
277
310
|
sql_extended = rolap_cell.getDrillThroughSQL(return_expressions, true)
|
@@ -314,9 +347,11 @@ module Mondrian
|
|
314
347
|
|
315
348
|
return_fields.size.times do |i|
|
316
349
|
column_alias = return_fields[i][:column_alias]
|
350
|
+
column_expression = return_fields[i][:column_expression]
|
351
|
+
quoted_table_name = return_fields[i][:quoted_table_name]
|
317
352
|
new_select_columns <<
|
318
|
-
if column_expression
|
319
|
-
new_order_by_columns << column_expression
|
353
|
+
if column_expression && (!quoted_table_name || extended_from.include?(quoted_table_name))
|
354
|
+
new_order_by_columns << column_expression unless return_fields[i][:name].start_with?('_level:')
|
320
355
|
new_group_by_columns << column_expression if group_by && return_fields[i][:type] != :measure
|
321
356
|
"#{column_expression} AS #{column_alias}"
|
322
357
|
else
|
@@ -337,12 +372,9 @@ module Mondrian
|
|
337
372
|
outer_join_from_parts = extended_from.split(/,\s*/) - new_from_parts
|
338
373
|
where_parts = extended_where.split(' and ')
|
339
374
|
|
340
|
-
|
341
|
-
# where join with detailed level table should be constructed first
|
342
|
-
outer_join_from_parts.reverse.each do |part|
|
375
|
+
outer_join_from_parts.each do |part|
|
343
376
|
part_elements = part.split(/\s+/)
|
344
377
|
# first is original table, then optional 'as' and the last is alias
|
345
|
-
table_name = part_elements.first
|
346
378
|
table_alias = part_elements.last
|
347
379
|
join_conditions = where_parts.select do |where_part|
|
348
380
|
where_part.include?(" = #{table_alias}.")
|
@@ -368,7 +400,7 @@ module Mondrian
|
|
368
400
|
sql = "select #{new_select} from #{new_from} where #{new_where}"
|
369
401
|
sql << " group by #{new_group_by}" unless new_group_by.empty?
|
370
402
|
sql << " order by #{new_order_by}" unless new_order_by.empty?
|
371
|
-
sql
|
403
|
+
[sql, return_fields]
|
372
404
|
end
|
373
405
|
|
374
406
|
def self.parse_return_fields(result, params)
|
@@ -396,8 +428,14 @@ module Mondrian
|
|
396
428
|
|
397
429
|
# Old versions of Oracle had a limit of 30 character identifiers.
|
398
430
|
# Do not limit it for other databases (as e.g. in MySQL aliases can be longer than column names)
|
399
|
-
max_alias_length = dialect.getMaxColumnNameLength
|
400
|
-
max_alias_length = nil if max_alias_length && max_alias_length > 30
|
431
|
+
max_alias_length = dialect.getMaxColumnNameLength # 0 means that there is no limit
|
432
|
+
max_alias_length = nil if max_alias_length && (max_alias_length > 30 || max_alias_length == 0)
|
433
|
+
sql_options = {
|
434
|
+
dialect: dialect,
|
435
|
+
sql_query: sql_query,
|
436
|
+
max_alias_length: max_alias_length,
|
437
|
+
params: params
|
438
|
+
}
|
401
439
|
|
402
440
|
return_fields.size.times do |i|
|
403
441
|
member_full_name = return_fields[i][:member_full_name]
|
@@ -418,44 +456,7 @@ module Mondrian
|
|
418
456
|
raise ArgumentError, "return field #{member_full_name} should be level or measure"
|
419
457
|
end
|
420
458
|
|
421
|
-
return_fields[i]
|
422
|
-
when :name
|
423
|
-
if level_or_member.respond_to? :getNameExp
|
424
|
-
level_or_member.getNameExp.getExpression sql_query
|
425
|
-
end
|
426
|
-
when :property
|
427
|
-
if property = level_or_member.getProperties.to_a.detect{|p| p.getName == return_fields[i][:name]}
|
428
|
-
# property.getExp is a protected method therefore
|
429
|
-
# use a workaround to get the value from the field
|
430
|
-
f = property.java_class.declared_field("exp")
|
431
|
-
f.accessible = true
|
432
|
-
if column = f.value(property)
|
433
|
-
column.getExpression sql_query
|
434
|
-
end
|
435
|
-
end
|
436
|
-
else
|
437
|
-
if level_or_member.respond_to? :getKeyExp
|
438
|
-
return_fields[i][:type] = :key
|
439
|
-
level_or_member.getKeyExp.getExpression sql_query
|
440
|
-
else
|
441
|
-
return_fields[i][:type] = :measure
|
442
|
-
column_expression = level_or_member.getMondrianDefExpression.getExpression sql_query
|
443
|
-
if params[:group_by]
|
444
|
-
level_or_member.getAggregator.getExpression column_expression
|
445
|
-
else
|
446
|
-
column_expression
|
447
|
-
end
|
448
|
-
end
|
449
|
-
end
|
450
|
-
|
451
|
-
column_alias = if return_fields[i][:type] == :key
|
452
|
-
"#{return_fields[i][:name]} (Key)"
|
453
|
-
else
|
454
|
-
return_fields[i][:name]
|
455
|
-
end
|
456
|
-
return_fields[i][:column_alias] = dialect.quoteIdentifier(
|
457
|
-
max_alias_length ? column_alias[0, max_alias_length] : column_alias
|
458
|
-
)
|
459
|
+
add_sql_attributes return_fields[i], sql_options
|
459
460
|
end
|
460
461
|
end
|
461
462
|
|
@@ -479,8 +480,76 @@ module Mondrian
|
|
479
480
|
end
|
480
481
|
end
|
481
482
|
|
483
|
+
if params[:role_name].present?
|
484
|
+
add_role_restricition_fields return_fields, sql_options
|
485
|
+
end
|
486
|
+
|
482
487
|
[nonempty_columns, return_fields]
|
483
488
|
end
|
489
|
+
|
490
|
+
def self.add_sql_attributes(field, options = {})
|
491
|
+
member = field[:member]
|
492
|
+
dialect = options[:dialect]
|
493
|
+
sql_query = options[:sql_query]
|
494
|
+
max_alias_length = options[:max_alias_length]
|
495
|
+
params = options[:params]
|
496
|
+
|
497
|
+
if table_name = (member.try(:getTableName) || member.try(:getMondrianDefExpression).try(:table))
|
498
|
+
field[:quoted_table_name] = dialect.quoteIdentifier(table_name)
|
499
|
+
end
|
500
|
+
|
501
|
+
field[:column_expression] =
|
502
|
+
case field[:type]
|
503
|
+
when :name
|
504
|
+
if member.respond_to? :getNameExp
|
505
|
+
member.getNameExp.getExpression sql_query
|
506
|
+
end
|
507
|
+
when :property
|
508
|
+
if property = member.getProperties.to_a.detect { |p| p.getName == field[:name] }
|
509
|
+
# property.getExp is a protected method therefore
|
510
|
+
# use a workaround to get the value from the field
|
511
|
+
f = property.java_class.declared_field("exp")
|
512
|
+
f.accessible = true
|
513
|
+
if column = f.value(property)
|
514
|
+
column.getExpression sql_query
|
515
|
+
end
|
516
|
+
end
|
517
|
+
when :name_or_key
|
518
|
+
member.getNameExp&.getExpression(sql_query) || member.getKeyExp&.getExpression(sql_query)
|
519
|
+
else
|
520
|
+
if member.respond_to? :getKeyExp
|
521
|
+
field[:type] = :key
|
522
|
+
member.getKeyExp.getExpression sql_query
|
523
|
+
else
|
524
|
+
field[:type] = :measure
|
525
|
+
column_expression = member.getMondrianDefExpression.getExpression sql_query
|
526
|
+
if params[:group_by]
|
527
|
+
member.getAggregator.getExpression column_expression
|
528
|
+
else
|
529
|
+
column_expression
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
column_alias = field[:type] == :key ? "#{field[:name]} (Key)" : field[:name]
|
535
|
+
field[:column_alias] = dialect.quoteIdentifier(
|
536
|
+
max_alias_length ? column_alias[0, max_alias_length] : column_alias
|
537
|
+
)
|
538
|
+
end
|
539
|
+
|
540
|
+
def self.add_role_restricition_fields(fields, options = {})
|
541
|
+
# For each unique level field add a set of fields to be able to build level member full name from database query results
|
542
|
+
fields.map { |f| f[:member] }.uniq.each_with_index do |level_or_member, i|
|
543
|
+
next if level_or_member.is_a?(Java::MondrianOlap::Member)
|
544
|
+
current_level = level_or_member
|
545
|
+
loop do
|
546
|
+
# Create an additional field name using a pattern "_level:<Fieldset ID>:<Level depth>"
|
547
|
+
fields << {member: current_level, type: :name_or_key, name: "_level:#{i}:#{current_level.getDepth}"}
|
548
|
+
add_sql_attributes fields.last, options
|
549
|
+
break unless (current_level = current_level.getParentLevel) and !current_level.isAll
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|
484
553
|
end
|
485
554
|
|
486
555
|
def self.java_to_ruby_value(value, column_type = nil)
|
data/lib/mondrian/olap/schema.rb
CHANGED
@@ -95,7 +95,9 @@ module Mondrian
|
|
95
95
|
:type,
|
96
96
|
# The name of the column in the fact table which joins to the leaf level of this dimension.
|
97
97
|
# Required in a private Dimension or a DimensionUsage, but not in a public Dimension.
|
98
|
-
:foreign_key
|
98
|
+
:foreign_key,
|
99
|
+
# Flag to mark this dimension as a high cardinality one and avoid caching.
|
100
|
+
:high_cardinality
|
99
101
|
data_dictionary_names :foreign_key # values in XML will be uppercased when using Oracle driver
|
100
102
|
elements :annotations, :hierarchy
|
101
103
|
end
|
@@ -113,7 +115,9 @@ module Mondrian
|
|
113
115
|
:usage_prefix,
|
114
116
|
# The name of the column in the fact table which joins to the leaf level of this dimension.
|
115
117
|
# Required in a private Dimension or a DimensionUsage, but not in a public Dimension.
|
116
|
-
:foreign_key
|
118
|
+
:foreign_key,
|
119
|
+
# Flag to mark this dimensions as a high cardinality one and avoid caching.
|
120
|
+
:high_cardinality
|
117
121
|
data_dictionary_names :usage_prefix, :foreign_key # values in XML will be uppercased when using Oracle driver
|
118
122
|
elements :annotations
|
119
123
|
|
@@ -347,7 +351,9 @@ module Mondrian
|
|
347
351
|
# Name of the cube which the dimension belongs to, or unspecified if the dimension is shared
|
348
352
|
:cube_name,
|
349
353
|
# Whether this dimension is visible in the user-interface. Default true.
|
350
|
-
:visible
|
354
|
+
:visible,
|
355
|
+
# Flag to mark this dimensions as a high cardinality one and avoid caching.
|
356
|
+
:high_cardinality
|
351
357
|
elements :annotations
|
352
358
|
end
|
353
359
|
|