mondrian-olap 1.1.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 +33 -0
- data/LICENSE-Mondrian.txt +87 -0
- data/LICENSE.txt +1 -1
- data/README.md +4 -4
- data/VERSION +1 -1
- data/lib/mondrian/jars/guava-17.0.jar +0 -0
- 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 +126 -73
- 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 +132 -56
- data/lib/mondrian/olap/schema.rb +9 -3
- data/lib/mondrian/olap/schema_element.rb +6 -3
- data/lib/mondrian/olap/schema_udf.rb +8 -82
- data/lib/mondrian/olap.rb +11 -7
- data/spec/connection_role_spec.rb +4 -1
- data/spec/connection_spec.rb +38 -5
- data/spec/cube_cache_control_spec.rb +7 -17
- data/spec/cube_spec.rb +36 -2
- data/spec/fixtures/MondrianTest.xml +40 -6
- data/spec/fixtures/MondrianTestOracle.xml +40 -6
- data/spec/mondrian_spec.rb +203 -1
- data/spec/query_spec.rb +94 -25
- data/spec/rake_tasks.rb +319 -165
- data/spec/schema_definition_spec.rb +8 -241
- data/spec/spec_helper.rb +330 -112
- data/spec/support/data/customers.csv +10902 -0
- data/spec/support/data/product_classes.csv +101 -0
- data/spec/support/data/products.csv +101 -0
- data/spec/support/data/promotions.csv +11 -0
- data/spec/support/data/sales.csv +101 -0
- data/spec/support/data/time.csv +731 -0
- data/spec/support/data/warehouse.csv +101 -0
- data/spec/support/matchers/be_like.rb +1 -0
- metadata +42 -83
- data/LICENSE-Mondrian.html +0 -259
- data/lib/mondrian/jars/log4j-1.2.17.jar +0 -0
- data/lib/mondrian/jars/log4j.properties +0 -3
- data/lib/mondrian/jars/mondrian-8.3.0.5.jar +0 -0
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)
|
@@ -394,6 +426,17 @@ module Mondrian
|
|
394
426
|
end
|
395
427
|
end
|
396
428
|
|
429
|
+
# Old versions of Oracle had a limit of 30 character identifiers.
|
430
|
+
# Do not limit it for other databases (as e.g. in MySQL aliases can be longer than column names)
|
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
|
+
}
|
439
|
+
|
397
440
|
return_fields.size.times do |i|
|
398
441
|
member_full_name = return_fields[i][:member_full_name]
|
399
442
|
begin
|
@@ -402,7 +445,7 @@ module Mondrian
|
|
402
445
|
raise ArgumentError, "invalid return field #{member_full_name}"
|
403
446
|
end
|
404
447
|
|
405
|
-
# if this is property field then the name is
|
448
|
+
# if this is property field then the name is initialized already
|
406
449
|
return_fields[i][:name] ||= segment_list.to_a.last.name
|
407
450
|
level_or_member = schema_reader.lookupCompound rolap_cube, segment_list, false, 0
|
408
451
|
return_fields[i][:member] = level_or_member
|
@@ -413,42 +456,7 @@ module Mondrian
|
|
413
456
|
raise ArgumentError, "return field #{member_full_name} should be level or measure"
|
414
457
|
end
|
415
458
|
|
416
|
-
return_fields[i]
|
417
|
-
when :name
|
418
|
-
if level_or_member.respond_to? :getNameExp
|
419
|
-
level_or_member.getNameExp.getExpression sql_query
|
420
|
-
end
|
421
|
-
when :property
|
422
|
-
if property = level_or_member.getProperties.to_a.detect{|p| p.getName == return_fields[i][:name]}
|
423
|
-
# property.getExp is a protected method therefore
|
424
|
-
# use a workaround to get the value from the field
|
425
|
-
f = property.java_class.declared_field("exp")
|
426
|
-
f.accessible = true
|
427
|
-
if column = f.value(property)
|
428
|
-
column.getExpression sql_query
|
429
|
-
end
|
430
|
-
end
|
431
|
-
else
|
432
|
-
if level_or_member.respond_to? :getKeyExp
|
433
|
-
return_fields[i][:type] = :key
|
434
|
-
level_or_member.getKeyExp.getExpression sql_query
|
435
|
-
else
|
436
|
-
return_fields[i][:type] = :measure
|
437
|
-
column_expression = level_or_member.getMondrianDefExpression.getExpression sql_query
|
438
|
-
if params[:group_by]
|
439
|
-
level_or_member.getAggregator.getExpression column_expression
|
440
|
-
else
|
441
|
-
column_expression
|
442
|
-
end
|
443
|
-
end
|
444
|
-
end
|
445
|
-
|
446
|
-
column_alias = if return_fields[i][:type] == :key
|
447
|
-
"#{return_fields[i][:name]} (Key)"
|
448
|
-
else
|
449
|
-
return_fields[i][:name]
|
450
|
-
end
|
451
|
-
return_fields[i][:column_alias] = dialect.quoteIdentifier(column_alias)
|
459
|
+
add_sql_attributes return_fields[i], sql_options
|
452
460
|
end
|
453
461
|
end
|
454
462
|
|
@@ -472,8 +480,76 @@ module Mondrian
|
|
472
480
|
end
|
473
481
|
end
|
474
482
|
|
483
|
+
if params[:role_name].present?
|
484
|
+
add_role_restricition_fields return_fields, sql_options
|
485
|
+
end
|
486
|
+
|
475
487
|
[nonempty_columns, return_fields]
|
476
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
|
477
553
|
end
|
478
554
|
|
479
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
|
|
@@ -140,8 +140,8 @@ module Mondrian
|
|
140
140
|
|
141
141
|
def xmlized_attributes(options)
|
142
142
|
# data dictionary values should be in uppercase if schema defined with :upcase_data_dictionary => true
|
143
|
-
# or by default when using Oracle or
|
144
|
-
upcase_attributes = if options[:upcase_data_dictionary].nil? && %w(oracle
|
143
|
+
# or by default when using Oracle or Snowflake driver (can be overridden by :upcase_data_dictionary => false)
|
144
|
+
upcase_attributes = if options[:upcase_data_dictionary].nil? && %w(oracle snowflake).include?(options[:driver]) ||
|
145
145
|
options[:upcase_data_dictionary]
|
146
146
|
self.class.data_dictionary_names
|
147
147
|
else
|
@@ -149,7 +149,10 @@ module Mondrian
|
|
149
149
|
end
|
150
150
|
hash = {}
|
151
151
|
@attributes.each do |attr, value|
|
152
|
-
|
152
|
+
# Support value calculation in parallel threads.
|
153
|
+
# value could be either Thread or a future object from concurrent-ruby
|
154
|
+
value = value.value if value.respond_to?(:value)
|
155
|
+
value = value.upcase if upcase_attributes.include?(attr) && value.is_a?(String)
|
153
156
|
hash[
|
154
157
|
# camelcase attribute name
|
155
158
|
attr.to_s.gsub(/_([^_]+)/){|m| $1.capitalize}
|
@@ -9,21 +9,8 @@ module Mondrian
|
|
9
9
|
end
|
10
10
|
|
11
11
|
module ScriptElements
|
12
|
-
def javascript(text)
|
13
|
-
script text, :language => 'JavaScript'
|
14
|
-
end
|
15
|
-
|
16
12
|
private
|
17
13
|
|
18
|
-
def coffeescript_function(arguments_string, text)
|
19
|
-
# construct function to ensure that last expression is returned
|
20
|
-
coffee_text = "#{arguments_string} ->\n" << text.gsub(/^/, ' ')
|
21
|
-
javascript_text = CoffeeScript.compile(coffee_text, :bare => true)
|
22
|
-
# remove function definition first and last lines
|
23
|
-
javascript_text = javascript_text.strip.lines.to_a[1..-2].join
|
24
|
-
javascript javascript_text
|
25
|
-
end
|
26
|
-
|
27
14
|
def ruby(*options, &block)
|
28
15
|
udf_class_name = if options.include?(:shared)
|
29
16
|
"#{name.capitalize}Udf"
|
@@ -74,64 +61,13 @@ module Mondrian
|
|
74
61
|
|
75
62
|
class UserDefinedFunction < SchemaElement
|
76
63
|
include ScriptElements
|
64
|
+
|
77
65
|
attributes :name, # Name with which the user-defined function will be referenced in MDX expressions.
|
78
|
-
# Name of the class which
|
79
|
-
# Must implement the mondrian.spi.UserDefinedFunction
|
66
|
+
# Name of the class which implements this user-defined function.
|
67
|
+
# Must implement the mondrian.spi.UserDefinedFunction interface.
|
80
68
|
:class_name
|
81
69
|
elements :script
|
82
70
|
|
83
|
-
def coffeescript(text)
|
84
|
-
coffee_text = "__udf__ = {\n" << text << "}\n"
|
85
|
-
javascript_text = CoffeeScript.compile(coffee_text, :bare => true)
|
86
|
-
javascript_text << <<-JS
|
87
|
-
|
88
|
-
__udf__.parameters || (__udf__.parameters = []);
|
89
|
-
__udf__.returns || (__udf__.returns = "Scalar");
|
90
|
-
var __scalarTypes__ = {"Numeric":true,"String":true,"Boolean":true,"DateTime":true,"Decimal":true,"Scalar":true};
|
91
|
-
function __getType__(type) {
|
92
|
-
if (__scalarTypes__[type]) {
|
93
|
-
return new mondrian.olap.type[type+"Type"];
|
94
|
-
} else if (type === "Member") {
|
95
|
-
return mondrian.olap.type.MemberType.Unknown;
|
96
|
-
} else {
|
97
|
-
return null;
|
98
|
-
}
|
99
|
-
}
|
100
|
-
function getParameterTypes() {
|
101
|
-
var parameters = __udf__.parameters || [],
|
102
|
-
types = [];
|
103
|
-
for (var i = 0, len = parameters.length; i < len; i++) {
|
104
|
-
types.push(__getType__(parameters[i]))
|
105
|
-
}
|
106
|
-
return types;
|
107
|
-
}
|
108
|
-
function getReturnType(parameterTypes) {
|
109
|
-
var returns = __udf__.returns || "Scalar";
|
110
|
-
return __getType__(returns);
|
111
|
-
}
|
112
|
-
if (__udf__.syntax) {
|
113
|
-
function getSyntax() {
|
114
|
-
return mondrian.olap.Syntax[__udf__.syntax];
|
115
|
-
}
|
116
|
-
}
|
117
|
-
function execute(evaluator, args) {
|
118
|
-
var parameters = __udf__.parameters || [],
|
119
|
-
values = [],
|
120
|
-
value;
|
121
|
-
for (var i = 0, len = parameters.length; i < len; i++) {
|
122
|
-
if (__scalarTypes__[parameters[i]]) {
|
123
|
-
value = args[i].evaluateScalar(evaluator);
|
124
|
-
} else {
|
125
|
-
value = args[i].evaluate(evaluator);
|
126
|
-
}
|
127
|
-
values.push(value);
|
128
|
-
}
|
129
|
-
return __udf__.execute.apply(__udf__, values);
|
130
|
-
}
|
131
|
-
JS
|
132
|
-
javascript javascript_text
|
133
|
-
end
|
134
|
-
|
135
71
|
class RubyUdfBase
|
136
72
|
include Java::mondrian.spi.UserDefinedFunction
|
137
73
|
def self.function_name=(name); @function_name = name; end
|
@@ -225,7 +161,7 @@ JS
|
|
225
161
|
arguments_array_class = java.lang.Class.forName "[Lmondrian.spi.UserDefinedFunction$Argument;", true, class_loader
|
226
162
|
add_method_signature("execute", [java.lang.Object, Java::mondrian.olap.Evaluator, arguments_array_class])
|
227
163
|
|
228
|
-
# Override this
|
164
|
+
# Override this method if evaluator is needed
|
229
165
|
def call_with_evaluator(evaluator, *values)
|
230
166
|
call(*values)
|
231
167
|
end
|
@@ -294,10 +230,6 @@ JS
|
|
294
230
|
end
|
295
231
|
end
|
296
232
|
|
297
|
-
def coffeescript(text)
|
298
|
-
coffeescript_function('(value)', text)
|
299
|
-
end
|
300
|
-
|
301
233
|
def ruby(*options, &block)
|
302
234
|
ruby_formatter(options, Java::mondrian.spi.CellFormatter, 'formatCell', [java.lang.String, java.lang.Object], &block)
|
303
235
|
end
|
@@ -308,12 +240,9 @@ JS
|
|
308
240
|
attributes :class_name
|
309
241
|
elements :script
|
310
242
|
|
311
|
-
def coffeescript(text)
|
312
|
-
coffeescript_function('(member)', text)
|
313
|
-
end
|
314
|
-
|
315
243
|
def ruby(*options, &block)
|
316
|
-
ruby_formatter(options, Java::mondrian.spi.MemberFormatter, 'formatMember',
|
244
|
+
ruby_formatter(options, Java::mondrian.spi.MemberFormatter, 'formatMember',
|
245
|
+
[java.lang.String, Java::mondrian.olap.Member], &block)
|
317
246
|
end
|
318
247
|
end
|
319
248
|
|
@@ -322,12 +251,9 @@ JS
|
|
322
251
|
attributes :class_name
|
323
252
|
elements :script
|
324
253
|
|
325
|
-
def coffeescript(text)
|
326
|
-
coffeescript_function('(member,propertyName,propertyValue)', text)
|
327
|
-
end
|
328
|
-
|
329
254
|
def ruby(*options, &block)
|
330
|
-
ruby_formatter(options, Java::mondrian.spi.PropertyFormatter, 'formatProperty',
|
255
|
+
ruby_formatter(options, Java::mondrian.spi.PropertyFormatter, 'formatProperty',
|
256
|
+
[java.lang.String, Java::mondrian.olap.Member, java.lang.String, java.lang.Object], &block)
|
331
257
|
end
|
332
258
|
end
|
333
259
|
|
data/lib/mondrian/olap.rb
CHANGED
@@ -1,18 +1,22 @@
|
|
1
1
|
require 'java'
|
2
2
|
require 'nokogiri'
|
3
3
|
|
4
|
+
{
|
5
|
+
# Do not register MondrianOlap4jDriver
|
6
|
+
"mondrian.olap4j.registerDriver" => false,
|
7
|
+
# Do not register log3j2 MBean
|
8
|
+
"log4j2.disable.jmx" => true
|
9
|
+
}.each do |key, value|
|
10
|
+
if java.lang.System.getProperty(key).nil?
|
11
|
+
java.lang.System.setProperty(key, value.to_s)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
4
15
|
directory = File.expand_path("../jars", __FILE__)
|
5
16
|
Dir["#{directory}/*.jar"].each do |file|
|
6
17
|
require file
|
7
18
|
end
|
8
19
|
|
9
|
-
unless java.lang.System.getProperty("log4j.configuration")
|
10
|
-
file_uri = java.io.File.new("#{directory}/log4j.properties").toURI.to_s
|
11
|
-
java.lang.System.setProperty("log4j.configuration", file_uri)
|
12
|
-
end
|
13
|
-
# register Mondrian olap4j driver
|
14
|
-
Java::mondrian.olap4j.MondrianOlap4jDriver
|
15
|
-
|
16
20
|
%w(error connection query result schema schema_udf cube).each do |file|
|
17
21
|
require "mondrian/olap/#{file}"
|
18
22
|
end
|
@@ -3,7 +3,7 @@ require "spec_helper"
|
|
3
3
|
describe "Connection role" do
|
4
4
|
|
5
5
|
describe "create connection" do
|
6
|
-
before(:
|
6
|
+
before(:all) do
|
7
7
|
@all_roles = [
|
8
8
|
@role_name = role_name = 'California manager',
|
9
9
|
@role_name2 = role_name2 = 'Dummy, with comma',
|
@@ -86,6 +86,9 @@ describe "Connection role" do
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
end
|
89
|
+
end
|
90
|
+
|
91
|
+
before(:each) do
|
89
92
|
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
|
90
93
|
end
|
91
94
|
|
data/spec/connection_spec.rb
CHANGED
@@ -12,7 +12,7 @@ describe "Connection" do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
it "should be successful" do
|
15
|
-
@olap.connect.should
|
15
|
+
@olap.connect.should == true
|
16
16
|
end
|
17
17
|
|
18
18
|
end
|
@@ -25,7 +25,7 @@ describe "Connection" do
|
|
25
25
|
@olap = Mondrian::OLAP::Connection.new(CONNECTION_PARAMS.merge(
|
26
26
|
:catalog_content => @schema_xml
|
27
27
|
))
|
28
|
-
@olap.connect.should
|
28
|
+
@olap.connect.should == true
|
29
29
|
end
|
30
30
|
|
31
31
|
end
|
@@ -49,11 +49,11 @@ describe "Connection" do
|
|
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'
|
52
|
-
when 'luciddb' then 'mondrian.spi.impl.LucidDbDialect'
|
53
|
-
when 'mssql' then 'mondrian.spi.impl.MicrosoftSqlServerDialect'
|
54
52
|
when 'sqlserver' then 'mondrian.spi.impl.MicrosoftSqlServerDialect'
|
55
53
|
when 'vertica' then 'mondrian.spi.impl.VerticaDialect'
|
56
|
-
when 'snowflake' then 'mondrian.spi.impl.
|
54
|
+
when 'snowflake' then 'mondrian.spi.impl.SnowflakeDialect'
|
55
|
+
when 'clickhouse' then 'mondrian.spi.impl.ClickHouseDialect'
|
56
|
+
when 'mariadb' then 'mondrian.spi.impl.MariaDBDialect'
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
@@ -90,4 +90,37 @@ describe "Connection" do
|
|
90
90
|
|
91
91
|
end
|
92
92
|
|
93
|
+
describe "jdbc_uri" do
|
94
|
+
before(:all) { @olap_connection = Mondrian::OLAP::Connection }
|
95
|
+
|
96
|
+
describe "SQL Server" do
|
97
|
+
it "should return a valid JDBC URI" do
|
98
|
+
@olap_connection.new(
|
99
|
+
driver: 'sqlserver',
|
100
|
+
host: 'example.com',
|
101
|
+
port: 1234,
|
102
|
+
instance: 'MSSQLSERVER',
|
103
|
+
database: 'example_db'
|
104
|
+
).jdbc_uri.should == 'jdbc:sqlserver://example.com:1234;databaseName=example_db;instanceName=MSSQLSERVER'
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should return a valid JDBC URI with instance name as property" do
|
108
|
+
@olap_connection.new(
|
109
|
+
driver: 'sqlserver',
|
110
|
+
host: 'example.com',
|
111
|
+
properties: {
|
112
|
+
instanceName: "MSSQLSERVER"
|
113
|
+
}
|
114
|
+
).jdbc_uri.should == 'jdbc:sqlserver://example.com;instanceName=MSSQLSERVER'
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should return a valid JDBC URI with enabled integratedSecurity" do
|
118
|
+
@olap_connection.new(
|
119
|
+
driver: 'sqlserver',
|
120
|
+
host: 'example.com',
|
121
|
+
integrated_security: 'true'
|
122
|
+
).jdbc_uri.should == 'jdbc:sqlserver://example.com;integratedSecurity=true'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
93
126
|
end
|
@@ -72,7 +72,8 @@ describe "Cube" do
|
|
72
72
|
@olap = Mondrian::OLAP::Connection.create(CONNECTION_PARAMS.merge :schema => @schema)
|
73
73
|
end
|
74
74
|
|
75
|
-
|
75
|
+
# Do not execute tests on analytical databases with slow individual inserts
|
76
|
+
describe 'cache', unless: %w(vertica snowflake clickhouse mariadb).include?(MONDRIAN_DRIVER) do
|
76
77
|
def qt(name)
|
77
78
|
@connection.quote_table_name(name.to_s)
|
78
79
|
end
|
@@ -87,9 +88,9 @@ describe "Cube" do
|
|
87
88
|
SQL
|
88
89
|
|
89
90
|
case MONDRIAN_DRIVER
|
90
|
-
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
|
91
|
+
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
|
91
92
|
@connection.execute 'CREATE TABLE sales_copy AS SELECT * FROM sales'
|
92
|
-
when '
|
93
|
+
when 'sqlserver'
|
93
94
|
# Use raw_connection.execute to avoid detecting this query as a SELECT query
|
94
95
|
# for which executeQuery JDBC method will fail
|
95
96
|
@connection.raw_connection.execute_update 'SELECT * INTO sales_copy FROM sales'
|
@@ -97,14 +98,8 @@ describe "Cube" do
|
|
97
98
|
end
|
98
99
|
|
99
100
|
after(:each) do
|
100
|
-
|
101
|
-
|
102
|
-
@connection.execute 'TRUNCATE TABLE sales'
|
103
|
-
@connection.execute 'INSERT INTO sales SELECT * FROM sales_copy'
|
104
|
-
when 'mssql', 'sqlserver'
|
105
|
-
@connection.execute 'TRUNCATE TABLE sales'
|
106
|
-
@connection.execute 'INSERT INTO sales SELECT * FROM sales_copy'
|
107
|
-
end
|
101
|
+
@connection.execute 'TRUNCATE TABLE sales'
|
102
|
+
@connection.execute 'INSERT INTO sales SELECT * FROM sales_copy'
|
108
103
|
|
109
104
|
@olap.flush_schema_cache
|
110
105
|
@olap.close
|
@@ -112,12 +107,7 @@ describe "Cube" do
|
|
112
107
|
end
|
113
108
|
|
114
109
|
after(:all) do
|
115
|
-
|
116
|
-
when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle', 'vertica', 'snowflake'
|
117
|
-
@connection.execute 'DROP TABLE sales_copy'
|
118
|
-
when 'mssql', 'sqlserver'
|
119
|
-
@connection.execute 'DROP TABLE sales_copy'
|
120
|
-
end
|
110
|
+
@connection.execute 'DROP TABLE sales_copy'
|
121
111
|
end
|
122
112
|
|
123
113
|
it 'should clear cache for deleted data at lower level with segments' do
|