mondrian-olap 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
|