mondrian-olap 0.8.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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