mondrian-olap 0.8.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ebf052cd103202ea4f2a37a914d13379776349bf
4
- data.tar.gz: af98519e88cc3f8a98c8fdf9538847115b499f7a
2
+ SHA256:
3
+ metadata.gz: 0df66cbf2bfa1f2a0a746568e7491398870121101d15b23d486e270877de3900
4
+ data.tar.gz: bfd14249c31c8f4b050c186cf5b355aaa6d91a8a042b89555d862868cfa52ee8
5
5
  SHA512:
6
- metadata.gz: 801ab9633c7b0e4efeb83ddc12d7ffebfe1966c6e65065c02692b0aa662efb5552c4fda1acc83a407bb82eaacaf76223ed03d6a4c863a0c7f8673bf32413a55e
7
- data.tar.gz: 0969711c0ffe2f39b4616b016718294b9b759333890e56f8ba3191f2b64472da645426ddc3e97e7b22b84afae7cb9e0599254f28be14db0875247f3968f37c4a
6
+ metadata.gz: 62219364cd64926f57429c4e6ff3373bc5fc19eaadd638544875407357ed11c5442716a6f5245078980762f31c170813bc8ed86e0c7dee6a54c1063c97827b03
7
+ data.tar.gz: c48f2a34b98c53955af10ccf093bcb73797e6f122282d70ffdf4a0cfeecded72a413bdf782ad68a58c9b44733bd9d4129bf865551fab894b98e1dcafeec717d8
@@ -1,3 +1,39 @@
1
+ ### 1.1.0 / 2019-11-09
2
+
3
+ * New features
4
+ * Upgrade to the latest Mondrian version 8.3.0.5
5
+ * Tested with JRuby 9.2.9.0
6
+ * Set query specific timeout
7
+ * Support for Vertica and Snowflake databases
8
+ * Bug fixes
9
+ * Handle and wrap TokenMgrError exception
10
+ * Patch for MONDRIAN-2660
11
+ * Patch for MONDRIAN-2661
12
+
13
+ ### 1.0.0 / 2019-03-04
14
+
15
+ * New features
16
+ * Upgrade to the latest Mondrian version 8.2.0.4
17
+ * Desupport Java 7, added support for Java 11, tested with JRuby 9.2.6.0
18
+ * Remove deprecated NativeException, Fixnum
19
+ * Added Mondrian query profiling option
20
+ * Added connection flush_schema method and flush_schema(schema_key) class method
21
+ * Added pin_schema_timeout connection parameter
22
+ * Bug fixes
23
+ * Fixed parsing of drill through queries with newlines
24
+ * Do not log Mondrian errors on the console
25
+ * Fixed handling of dimension names with escaped ]
26
+ * Patch for MONDRIAN-2641
27
+
28
+ ### 0.9.0 / 2017-10-07
29
+
30
+ * New features
31
+ * Upgraded to the latest Mondrian version 3.14.0.5
32
+ * Added an annotations_hash method for schema elements
33
+ * Bug fixes
34
+ * Fixed drill through SQL query generation
35
+ * Set a single role as a union role - used as a workaround for a Mondrian bug
36
+
1
37
  ### 0.8.0 / 2016-10-26
2
38
 
3
39
  * New features
@@ -1,6 +1,6 @@
1
1
  (The MIT License)
2
2
 
3
- Copyright (c) 2010-2016 Raimonds Simanovskis
3
+ Copyright (c) 2010-2019 Raimonds Simanovskis
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining
6
6
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,3 +1,6 @@
1
+ [![Travis CI status](https://travis-ci.org/rsim/mondrian-olap.svg?branch=master)](https://travis-ci.org/rsim/mondrian-olap)
2
+ [![AppVeyor status](https://ci.appveyor.com/api/projects/status/08xd4tyty2k3wxba/branch/master?svg=true)](https://ci.appveyor.com/project/rsim/mondrian-olap)
3
+
1
4
  mondrian-olap
2
5
  =============
3
6
 
@@ -270,9 +273,9 @@ See more examples of data access roles in `spec/connection_role_spec.rb`.
270
273
  REQUIREMENTS
271
274
  ------------
272
275
 
273
- mondrian-olap gem is compatible with JRuby versions 1.7 and 9.0 and Java 7 or 8 VM. mondrian-olap works only with JRuby and not with other Ruby implementations as it includes Mondrian OLAP Java libraries.
276
+ mondrian-olap gem is compatible with JRuby versions 1.7 and 9.x, and Java 8 and 11 VM. mondrian-olap works only with JRuby and not with other Ruby implementations as it includes Mondrian OLAP Java libraries.
274
277
 
275
- mondrian-olap supports MySQL, PostgreSQL, Oracle, LucidDB and Microsoft SQL Server databases as well as other databases that are supported by Mondrian OLAP engine (using jdbc_driver and jdbc_url connection parameters). When using MySQL, PostgreSQL or LucidDB databases then install jdbc-mysql, jdbc-postgres or jdbc-luciddb gem and require "jdbc/mysql", "jdbc/postgres" or "jdbc/luciddb" to load the corresponding JDBC database driver. When using Oracle then include Oracle JDBC driver (`ojdbc7.jar` for Java 7) in `CLASSPATH` or copy to `JRUBY_HOME/lib` or require it in application manually. When using SQL Server you can choose between the jTDS or Microsoft JDBC drivers. If you use jTDS require "jdbc/jtds". If you use the Microsoft JDBC driver include `sqljdbc.jar` or `sqljdbc4.jar` in `CLASSPATH` or copy to `JRUBY_HOME/lib` or require it in application manually.
278
+ mondrian-olap supports MySQL, PostgreSQL, Oracle, LucidDB and Microsoft SQL Server databases as well as other databases that are supported by Mondrian OLAP engine (using jdbc_driver and jdbc_url connection parameters). When using MySQL, PostgreSQL or LucidDB databases then install jdbc-mysql, jdbc-postgres or jdbc-luciddb gem and require "jdbc/mysql", "jdbc/postgres" or "jdbc/luciddb" to load the corresponding JDBC database driver. When using Oracle then include Oracle JDBC driver (`ojdbc8.jar` for Java 8) in `CLASSPATH` or copy to `JRUBY_HOME/lib` or require it in application manually. When using SQL Server you can choose between the jTDS or Microsoft JDBC drivers. If you use jTDS require "jdbc/jtds". If you use the Microsoft JDBC driver then include the latest `mssql-jdbc-*.jar` in `CLASSPATH` or copy to `JRUBY_HOME/lib` or require it in application manually.
276
279
 
277
280
  INSTALL
278
281
  -------
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.0
1
+ 1.1.0
@@ -1,5 +1,3 @@
1
- # Logs errors on the console
1
+ # By default do not log anything
2
2
  #
3
- log4j.rootLogger = ERROR, A1
4
- log4j.appender.A1 = org.apache.log4j.ConsoleAppender
5
- log4j.appender.A1.layout=org.apache.log4j.PatternLayout
3
+ log4j.rootLogger=OFF
@@ -7,10 +7,10 @@ module Mondrian
7
7
  connection
8
8
  end
9
9
 
10
- attr_reader :raw_connection, :raw_catalog, :raw_schema,
10
+ attr_reader :raw_connection, :raw_mondrian_connection, :raw_catalog, :raw_schema,
11
11
  :raw_schema_reader, :raw_cache_control
12
12
 
13
- def initialize(params={})
13
+ def initialize(params = {})
14
14
  @params = params
15
15
  @driver = params[:driver]
16
16
  @connected = false
@@ -55,8 +55,9 @@ module Mondrian
55
55
  @raw_catalog = @raw_connection.getOlapCatalog
56
56
  # currently it is assumed that there is just one schema per connection catalog
57
57
  @raw_schema = @raw_catalog.getSchemas.first
58
- @raw_schema_reader = @raw_connection.getMondrianConnection.getSchemaReader
59
- @raw_cache_control = @raw_connection.getMondrianConnection.getCacheControl(nil)
58
+ @raw_mondrian_connection = @raw_connection.getMondrianConnection
59
+ @raw_schema_reader = @raw_mondrian_connection.getSchemaReader
60
+ @raw_cache_control = @raw_mondrian_connection.getCacheControl(nil)
60
61
  @connected = true
61
62
  true
62
63
  end
@@ -67,17 +68,24 @@ module Mondrian
67
68
  end
68
69
 
69
70
  def close
71
+ @raw_jdbc_connection = @raw_catalog = @raw_schema = @raw_mondrian_connection = nil
72
+ @raw_schema_reader = @raw_cache_control = nil
70
73
  @raw_connection.close
74
+ @raw_connection = nil
71
75
  @connected = false
72
- @raw_connection = @raw_jdbc_connection = nil
73
76
  true
74
77
  end
75
78
 
76
79
  def execute(query_string, parameters = {})
77
- Error.wrap_native_exception do
80
+ options = {}
81
+ Error.wrap_native_exception(options) do
82
+ start_time = Time.now
78
83
  statement = @raw_connection.prepareOlapStatement(query_string)
84
+ options[:profiling_statement] = statement if parameters[:profiling]
79
85
  set_statement_parameters(statement, parameters)
80
- Result.new(self, statement.executeQuery())
86
+ raw_cell_set = statement.executeQuery()
87
+ total_duration = ((Time.now - start_time) * 1000).to_i
88
+ Result.new(self, raw_cell_set, profiling_handler: statement.getProfileHandler, total_duration: total_duration)
81
89
  end
82
90
  end
83
91
 
@@ -99,6 +107,36 @@ module Mondrian
99
107
  Query.from(self, cube_name)
100
108
  end
101
109
 
110
+ def raw_schema_key
111
+ @raw_mondrian_connection.getSchema.getKey
112
+ end
113
+
114
+ def schema_key
115
+ raw_schema_key.toString
116
+ end
117
+
118
+ def self.raw_schema_key(schema_key)
119
+ if schema_key =~ /\A<(.*), (.*)>\z/
120
+ schema_content_key = $1
121
+ connection_key = $2
122
+
123
+ cons = Java::mondrian.rolap.SchemaContentKey.java_class.declared_constructor(java.lang.String)
124
+ cons.accessible = true
125
+ raw_schema_content_key = cons.new_instance(schema_content_key)
126
+
127
+ cons = Java::mondrian.rolap.ConnectionKey.java_class.declared_constructor(java.lang.String)
128
+ cons.accessible = true
129
+ raw_connection_key = cons.new_instance(connection_key)
130
+
131
+ cons = Java::mondrian.rolap.SchemaKey.java_class.declared_constructor(
132
+ Java::mondrian.rolap.SchemaContentKey, Java::mondrian.rolap.ConnectionKey)
133
+ cons.accessible = true
134
+ cons.new_instance(raw_schema_content_key, raw_connection_key)
135
+ else
136
+ raise ArgumentError, "invalid schema key #{schema_key}"
137
+ end
138
+ end
139
+
102
140
  def cube_names
103
141
  @raw_schema.getCubes.map{|c| c.getName}
104
142
  end
@@ -109,12 +147,37 @@ module Mondrian
109
147
 
110
148
  # Will affect only the next created connection. If it is necessary to clear all schema cache then
111
149
  # flush_schema_cache should be called, then close and then new connection should be created.
150
+ # This method flushes schemas for all connections (clears the schema pool).
112
151
  def flush_schema_cache
113
- unwrapped_connection = @raw_connection.unwrap(Java::MondrianOlap::Connection.java_class)
114
- raw_cache_control = unwrapped_connection.getCacheControl(nil)
115
152
  raw_cache_control.flushSchemaCache
116
153
  end
117
154
 
155
+ def self.raw_schema_pool
156
+ method = Java::mondrian.rolap.RolapSchemaPool.java_class.declared_method('instance')
157
+ method.accessible = true
158
+ method.invoke_static
159
+ end
160
+
161
+ def self.flush_schema_cache
162
+ method = Java::mondrian.rolap.RolapSchemaPool.java_class.declared_method('clear')
163
+ method.accessible = true
164
+ method.invoke(raw_schema_pool)
165
+ end
166
+
167
+ # This method flushes the schema only for this connection (removes from the schema pool).
168
+ def flush_schema
169
+ if raw_mondrian_connection && (rolap_schema = raw_mondrian_connection.getSchema)
170
+ raw_cache_control.flushSchema(rolap_schema)
171
+ end
172
+ end
173
+
174
+ def self.flush_schema(schema_key)
175
+ method = Java::mondrian.rolap.RolapSchemaPool.java_class.declared_method('remove',
176
+ Java::mondrian.rolap.SchemaKey.java_class)
177
+ method.accessible = true
178
+ method.invoke(raw_schema_pool, raw_schema_key(schema_key))
179
+ end
180
+
118
181
  def available_role_names
119
182
  @raw_connection.getAvailableRoleNames.to_a
120
183
  end
@@ -139,7 +202,9 @@ module Mondrian
139
202
  Error.wrap_native_exception do
140
203
  # workaround to access non-public method (was not public when using inside Torquebox)
141
204
  # @raw_connection.setRoleNames(Array(names))
142
- @raw_connection.java_method(:setRoleNames, [Java::JavaUtil::List.java_class]).call(Array(names))
205
+ names = Array(names)
206
+ @raw_connection.java_method(:setRoleNames, [Java::JavaUtil::List.java_class]).call(names)
207
+ names
143
208
  end
144
209
  end
145
210
 
@@ -149,7 +214,7 @@ module Mondrian
149
214
 
150
215
  def locale=(locale)
151
216
  locale_elements = locale.to_s.split('_')
152
- raise ArgumentError, "invalid locale string #{locale.inspect}" unless [1,2,3].include?(locale_elements.length)
217
+ raise ArgumentError, "invalid locale string #{locale.inspect}" unless [1, 2, 3].include?(locale_elements.length)
153
218
  java_locale = Java::JavaUtil::Locale.new(*locale_elements)
154
219
  @raw_connection.setLocale(java_locale)
155
220
  end
@@ -230,6 +295,7 @@ module Mondrian
230
295
  string = "jdbc:mondrian:Jdbc=#{quote_string(jdbc_uri)};JdbcDrivers=#{jdbc_driver};"
231
296
  # by default use content checksum to reload schema when catalog has changed
232
297
  string << "UseContentChecksum=true;" unless @params[:use_content_checksum] == false
298
+ string << "PinSchemaTimeout=#{@params[:pin_schema_timeout]};" if @params[:pin_schema_timeout]
233
299
  if role = @params[:role] || @params[:roles]
234
300
  roles = Array(role).map{|r| r && r.to_s.gsub(',', ',,')}.compact
235
301
  string << "Role=#{quote_string(roles.join(','))};" unless roles.empty?
@@ -242,7 +308,7 @@ module Mondrian
242
308
 
243
309
  def jdbc_uri
244
310
  case @driver
245
- when 'mysql', 'postgresql'
311
+ when 'mysql', 'postgresql', 'vertica'
246
312
  uri = "jdbc:#{@driver}://#{@params[:host]}#{@params[:port] && ":#{@params[:port]}"}/#{@params[:database]}"
247
313
  uri << "?useUnicode=yes&characterEncoding=UTF-8" if @driver == 'mysql'
248
314
  if (properties = @params[:properties]).is_a?(Hash) && !properties.empty?
@@ -281,6 +347,15 @@ module Mondrian
281
347
  uri << ";applicationName=#{@params[:application_name]}" if @params[:application_name]
282
348
  uri << ";instanceName=#{@params[:instance_name]}" if @params[:instance_name]
283
349
  uri
350
+ when 'snowflake'
351
+ uri = "jdbc:snowflake://#{@params[:host]}#{@params[:port] && ":#{@params[:port]}"}/?db=#{@params[:database]}"
352
+ uri << "&schema=#{@params[:database_schema]}" if @params[:database_schema]
353
+ uri << "&warehouse=#{@params[:warehouse]}" if @params[:warehouse]
354
+ if (properties = @params[:properties]).is_a?(Hash) && !properties.empty?
355
+ uri << '&'
356
+ uri << properties.map{|k, v| "#{k}=#{v}"}.join('&')
357
+ end
358
+ uri
284
359
  when 'jdbc'
285
360
  @params[:jdbc_url] or raise ArgumentError, 'missing jdbc_url parameter'
286
361
  else
@@ -302,6 +377,10 @@ module Mondrian
302
377
  'net.sourceforge.jtds.jdbc.Driver'
303
378
  when 'sqlserver'
304
379
  'com.microsoft.sqlserver.jdbc.SQLServerDriver'
380
+ when 'vertica'
381
+ 'com.vertica.jdbc.Driver'
382
+ when 'snowflake'
383
+ 'net.snowflake.client.jdbc.SnowflakeDriver'
305
384
  when 'jdbc'
306
385
  @params[:jdbc_driver] or raise ArgumentError, 'missing jdbc_driver parameter'
307
386
  else
@@ -328,15 +407,14 @@ module Mondrian
328
407
  end
329
408
 
330
409
  def quote_string(string)
331
- "'#{string.gsub("'","''")}'"
410
+ "'#{string.gsub("'", "''")}'"
332
411
  end
333
412
 
334
413
  def set_statement_parameters(statement, parameters)
335
414
  if parameters && !parameters.empty?
415
+ parameters = parameters.dup
336
416
  # define addtional parameters which can be accessed from user defined functions
337
- if parameters[:define_parameters]
338
- parameters = parameters.dup
339
- define_parameters = parameters.delete(:define_parameters)
417
+ if define_parameters = parameters.delete(:define_parameters)
340
418
  query_validator = statement.getQuery.createValidator
341
419
  define_parameters.each do |dp_name, dp_value|
342
420
  dp_type_class = dp_value.is_a?(Numeric) ? Java::MondrianOlapType::NumericType : Java::MondrianOlapType::StringType
@@ -344,12 +422,30 @@ module Mondrian
344
422
  parameters[dp_name] = dp_value
345
423
  end
346
424
  end
425
+ if parameters.delete(:profiling)
426
+ statement.enableProfiling(ProfilingHandler.new)
427
+ end
428
+ if timeout = parameters.delete(:timeout)
429
+ statement.getQuery.setQueryTimeoutMillis(timeout * 1000)
430
+ end
347
431
  parameters.each do |parameter_name, value|
348
432
  statement.getQuery.setParameter(parameter_name, value)
349
433
  end
350
434
  end
351
435
  end
352
436
 
437
+ class ProfilingHandler
438
+ java_implements Java::mondrian.spi.ProfileHandler
439
+ attr_reader :plan
440
+ attr_reader :timing
441
+
442
+ java_signature 'void explain(String plan, mondrian.olap.QueryTiming timing)'
443
+ def explain(plan, timing)
444
+ @plan = plan
445
+ @timing = timing
446
+ end
447
+ end
448
+
353
449
  end
354
450
  end
355
451
  end
@@ -1,3 +1,5 @@
1
+ require 'forwardable'
2
+
1
3
  module Mondrian
2
4
  module OLAP
3
5
  module Annotated
@@ -400,13 +402,18 @@ module Mondrian
400
402
  end
401
403
 
402
404
  def cell_formatter_name
405
+ if cf = cell_formatter
406
+ cf.class.name.split('::').last.gsub(/Udf\z/, '')
407
+ end
408
+ end
409
+
410
+ def cell_formatter
403
411
  if dimension_type == :measures
404
412
  cube_measure = raw_member.unwrap(Java::MondrianOlap::Member.java_class)
405
413
  if value_formatter = cube_measure.getFormatter
406
414
  f = value_formatter.java_class.declared_field('cf')
407
415
  f.accessible = true
408
- cf = f.value(value_formatter)
409
- cf.class.name.split('::').last.gsub(/Udf\z/, '')
416
+ f.value(value_formatter)
410
417
  end
411
418
  end
412
419
  end
@@ -6,25 +6,43 @@ module Mondrian
6
6
  class Error < StandardError
7
7
  # root_cause will be nil if there is no cause for wrapped native error
8
8
  # root_cause_message will have either root_cause message or wrapped native error message
9
- attr_reader :native_error, :root_cause_message, :root_cause
9
+ attr_reader :native_error, :root_cause_message, :root_cause, :profiling_handler
10
10
 
11
- def initialize(native_error)
11
+ def initialize(native_error, options = {})
12
12
  @native_error = native_error
13
13
  get_root_cause
14
- super(native_error.message)
14
+ super(native_error.toString)
15
15
  add_root_cause_to_backtrace
16
+ get_profiling(options)
16
17
  end
17
18
 
18
- def self.wrap_native_exception
19
+ def self.wrap_native_exception(options = {})
19
20
  yield
20
- rescue NativeException => e
21
- if e.message =~ NATIVE_ERROR_REGEXP
22
- raise Mondrian::OLAP::Error.new(e)
21
+ # TokenMgrError for some unknown reason extends java.lang.Error which normally should not be rescued
22
+ rescue Java::JavaLang::Exception, Java::MondrianParser::TokenMgrError => e
23
+ if e.toString =~ NATIVE_ERROR_REGEXP
24
+ raise Mondrian::OLAP::Error.new(e, options)
23
25
  else
24
26
  raise
25
27
  end
26
28
  end
27
29
 
30
+ def profiling_plan
31
+ if profiling_handler && (plan = profiling_handler.plan)
32
+ plan.gsub("\r\n", "\n")
33
+ end
34
+ end
35
+
36
+ def profiling_timing
37
+ profiling_handler.timing if profiling_handler
38
+ end
39
+
40
+ def profiling_timing_string
41
+ if profiling_timing && (timing_string = profiling_timing.toString)
42
+ timing_string.gsub("\r\n", "\n")
43
+ end
44
+ end
45
+
28
46
  private
29
47
 
30
48
  def get_root_cause
@@ -44,7 +62,7 @@ module Mondrian
44
62
  bt = @native_error.backtrace
45
63
  if @root_cause
46
64
  root_cause_bt = Array(@root_cause.backtrace)
47
- root_cause_bt[0,10].reverse.each do |bt_line|
65
+ root_cause_bt[0, 10].reverse.each do |bt_line|
48
66
  bt.unshift "root cause: #{bt_line}"
49
67
  end
50
68
  bt.unshift "root cause: #{@root_cause.java_class.name}: #{@root_cause.message.chomp}"
@@ -52,6 +70,17 @@ module Mondrian
52
70
  set_backtrace bt
53
71
  end
54
72
 
73
+ def get_profiling(options)
74
+ if statement = options[:profiling_statement]
75
+ f = Java::mondrian.olap4j.MondrianOlap4jStatement.java_class.declared_field("openCellSet")
76
+ f.accessible = true
77
+ if cell_set = f.value(statement)
78
+ cell_set.close
79
+ @profiling_handler = statement.getProfileHandler
80
+ end
81
+ end
82
+ end
83
+
55
84
  end
56
85
  end
57
86
  end