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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +33 -0
  3. data/LICENSE-Mondrian.txt +87 -0
  4. data/LICENSE.txt +1 -1
  5. data/README.md +4 -4
  6. data/VERSION +1 -1
  7. data/lib/mondrian/jars/guava-17.0.jar +0 -0
  8. data/lib/mondrian/jars/log4j-api-2.17.1.jar +0 -0
  9. data/lib/mondrian/jars/log4j-core-2.17.1.jar +0 -0
  10. data/lib/mondrian/jars/log4j2-config.jar +0 -0
  11. data/lib/mondrian/jars/mondrian-9.3.0.0.jar +0 -0
  12. data/lib/mondrian/olap/connection.rb +126 -73
  13. data/lib/mondrian/olap/cube.rb +46 -4
  14. data/lib/mondrian/olap/error.rb +10 -2
  15. data/lib/mondrian/olap/query.rb +1 -0
  16. data/lib/mondrian/olap/result.rb +132 -56
  17. data/lib/mondrian/olap/schema.rb +9 -3
  18. data/lib/mondrian/olap/schema_element.rb +6 -3
  19. data/lib/mondrian/olap/schema_udf.rb +8 -82
  20. data/lib/mondrian/olap.rb +11 -7
  21. data/spec/connection_role_spec.rb +4 -1
  22. data/spec/connection_spec.rb +38 -5
  23. data/spec/cube_cache_control_spec.rb +7 -17
  24. data/spec/cube_spec.rb +36 -2
  25. data/spec/fixtures/MondrianTest.xml +40 -6
  26. data/spec/fixtures/MondrianTestOracle.xml +40 -6
  27. data/spec/mondrian_spec.rb +203 -1
  28. data/spec/query_spec.rb +94 -25
  29. data/spec/rake_tasks.rb +319 -165
  30. data/spec/schema_definition_spec.rb +8 -241
  31. data/spec/spec_helper.rb +330 -112
  32. data/spec/support/data/customers.csv +10902 -0
  33. data/spec/support/data/product_classes.csv +101 -0
  34. data/spec/support/data/products.csv +101 -0
  35. data/spec/support/data/promotions.csv +11 -0
  36. data/spec/support/data/sales.csv +101 -0
  37. data/spec/support/data/time.csv +731 -0
  38. data/spec/support/data/warehouse.csv +101 -0
  39. data/spec/support/matchers/be_like.rb +1 -0
  40. metadata +42 -83
  41. data/LICENSE-Mondrian.html +0 -259
  42. data/lib/mondrian/jars/log4j-1.2.17.jar +0 -0
  43. data/lib/mondrian/jars/log4j.properties +0 -3
  44. data/lib/mondrian/jars/mondrian-8.3.0.5.jar +0 -0
@@ -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
- new(raw_result_set)
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 = return_fields[i][: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
- # reverse outer_join_from_parts to support dimensions with several table joins
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 initilized already
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][:column_expression] = case return_fields[i][:type]
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)
@@ -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 LucidDB driver (can be overridden by :upcase_data_dictionary => false)
144
- upcase_attributes = if options[:upcase_data_dictionary].nil? && %w(oracle luciddb snowflake).include?(options[:driver]) ||
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
- value = value.upcase if upcase_attributes.include?(attr)
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 implemenets this user-defined function.
79
- # Must implement the mondrian.spi.UserDefinedFunction interface.
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 metho if evaluator is needed
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', [java.lang.String, Java::mondrian.olap.Member], &block)
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', [java.lang.String, Java::mondrian.olap.Member, java.lang.String, java.lang.Object], &block)
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(:each) do
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
 
@@ -12,7 +12,7 @@ describe "Connection" do
12
12
  end
13
13
 
14
14
  it "should be successful" do
15
- @olap.connect.should be_true
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 be_true
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.JdbcDialectImpl'
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
- describe 'cache', unless: MONDRIAN_DRIVER == 'luciddb' do
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', 'vertica', 'snowflake'
91
+ when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle'
91
92
  @connection.execute 'CREATE TABLE sales_copy AS SELECT * FROM sales'
92
- when 'mssql', 'sqlserver'
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
- case MONDRIAN_DRIVER
101
- when 'mysql', 'jdbc_mysql', 'postgresql', 'oracle', 'vertica', 'snowflake'
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
- case MONDRIAN_DRIVER
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