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.
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