mondrian-olap 1.1.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 +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
|