class Mondrian::OLAP::Connection

Constants

JDBC_DRIVER_CLASS
JDBC_SNOWFLAKE_PARAM_PROPERTIES
JDBC_SQLSERVER_PARAM_PROPERTIES
QUERY_BODY_PLAN_REGEXP

Starting from Mondrian 9.2 additional QueryBody plan string is added at the end which will be ignored.

Attributes

raw_cache_control[R]
raw_catalog[R]
raw_connection[R]
raw_mondrian_connection[R]
raw_schema[R]
raw_schema_reader[R]

Public Class Methods

create(params) click to toggle source
# File lib/mondrian/olap/connection.rb, line 4
def self.create(params)
  connection = new(params)
  connection.connect
  connection
end
flush_schema(schema_key) click to toggle source
# File lib/mondrian/olap/connection.rb, line 174
def self.flush_schema(schema_key)
  method = Java::mondrian.rolap.RolapSchemaPool.java_class.declared_method('remove',
    Java::mondrian.rolap.SchemaKey.java_class)
  method.accessible = true
  method.invoke(raw_schema_pool, raw_schema_key(schema_key))
end
flush_schema_cache() click to toggle source
# File lib/mondrian/olap/connection.rb, line 161
def self.flush_schema_cache
  method = Java::mondrian.rolap.RolapSchemaPool.java_class.declared_method('clear')
  method.accessible = true
  method.invoke(raw_schema_pool)
end
new(params = {}) click to toggle source
# File lib/mondrian/olap/connection.rb, line 13
def initialize(params = {})
  @params = params
  @driver = params[:driver]
  @connected = false
  @raw_connection = nil
end
raw_schema_key(schema_key) click to toggle source
# File lib/mondrian/olap/connection.rb, line 118
def self.raw_schema_key(schema_key)
  if schema_key =~ /\A<(.*), (.*)>\z/
    schema_content_key = $1
    connection_key = $2

    cons = Java::mondrian.rolap.SchemaContentKey.java_class.declared_constructor(java.lang.String)
    cons.accessible = true
    raw_schema_content_key = cons.new_instance(schema_content_key)

    cons = Java::mondrian.rolap.ConnectionKey.java_class.declared_constructor(java.lang.String)
    cons.accessible = true
    raw_connection_key = cons.new_instance(connection_key)

    cons = Java::mondrian.rolap.SchemaKey.java_class.declared_constructor(
      Java::mondrian.rolap.SchemaContentKey, Java::mondrian.rolap.ConnectionKey)
    cons.accessible = true
    cons.new_instance(raw_schema_content_key, raw_connection_key)
  else
    raise ArgumentError, "invalid schema key #{schema_key}"
  end
end
raw_schema_pool() click to toggle source
# File lib/mondrian/olap/connection.rb, line 155
def self.raw_schema_pool
  method = Java::mondrian.rolap.RolapSchemaPool.java_class.declared_method('instance')
  method.accessible = true
  method.invoke_static
end
shutdown_static_mondrian_server!() click to toggle source

Force shutdown of static MondrianServer, should not normally be used. Can be used in at_exit block if JRuby based plugin is unloaded from other Java application. WARNING: Mondrian will be unusable after calling this method!

# File lib/mondrian/olap/connection.rb, line 232
def self.shutdown_static_mondrian_server!
  static_mondrian_server = Java::MondrianOlap::MondrianServer.forId(nil)

  # force Mondrian to think that static_mondrian_server is not static MondrianServer
  mondrian_server_registry = Java::MondrianServer::MondrianServerRegistry::INSTANCE
  f = mondrian_server_registry.java_class.declared_field("staticServer")
  f.accessible = true
  f.set_value(mondrian_server_registry, nil)

  static_mondrian_server.shutdown

  # shut down expiring reference timer thread
  f = Java::MondrianUtil::ExpiringReference.java_class.declared_field("timer")
  f.accessible = true
  expiring_reference_timer = f.static_value.to_java
  expiring_reference_timer.cancel

  # shut down Mondrian Monitor
  cons = Java::MondrianServer.__send__(:"MonitorImpl$ShutdownCommand").java_class.declared_constructor
  cons.accessible = true
  shutdown_command = cons.new_instance.to_java

  cons = Java::MondrianServer.__send__(:"MonitorImpl$Handler").java_class.declared_constructor
  cons.accessible = true
  handler = cons.new_instance.to_java

  pair = Java::mondrian.util.Pair.new handler, shutdown_command

  f = Java::MondrianServer::MonitorImpl.java_class.declared_field("ACTOR")
  f.accessible = true
  monitor_actor = f.static_value.to_java

  f = monitor_actor.java_class.declared_field("eventQueue")
  f.accessible = true
  event_queue = f.value(monitor_actor)

  event_queue.put pair

  # shut down connection pool thread
  f = Java::mondrian.rolap.RolapConnectionPool.java_class.declared_field("instance")
  f.accessible = true
  rolap_connection_pool = f.static_value.to_java
  f = rolap_connection_pool.java_class.declared_field("mapConnectKeyToPool")
  f.accessible = true
  map_connect_key_to_pool = f.value(rolap_connection_pool)
  map_connect_key_to_pool.values.each do |pool|
    pool.close if pool && !pool.isClosed
  end

  # unregister MBean
  mbs = Java::JavaLangManagement::ManagementFactory.getPlatformMBeanServer
  mbean_name = Java::JavaxManagement::ObjectName.new("mondrian.server:type=Server-#{static_mondrian_server.getId}")
  begin
    mbs.unregisterMBean(mbean_name)
  rescue Java::JavaxManagement::InstanceNotFoundException
  end

  true
end

Public Instance Methods

available_role_names() click to toggle source
# File lib/mondrian/olap/connection.rb, line 181
def available_role_names
  @raw_connection.getAvailableRoleNames.to_a
end
close() click to toggle source
# File lib/mondrian/olap/connection.rb, line 70
def close
  @raw_jdbc_connection = @raw_catalog = @raw_schema = @raw_mondrian_connection = nil
  @raw_schema_reader = @raw_cache_control = nil
  @raw_connection.close
  @raw_connection = nil
  @connected = false
  true
end
connect() click to toggle source
# File lib/mondrian/olap/connection.rb, line 20
def connect
  Error.wrap_native_exception do
    # hack to call private constructor of MondrianOlap4jDriver
    # to avoid using DriverManager which fails to load JDBC drivers
    # because of not seeing JRuby required jar files
    cons = Java::MondrianOlap4j::MondrianOlap4jDriver.java_class.declared_constructor
    cons.accessible = true
    driver = cons.new_instance.to_java

    props = java.util.Properties.new
    props.setProperty('JdbcUser', @params[:username]) if @params[:username]
    props.setProperty('JdbcPassword', @params[:password]) if @params[:password]

    # on Oracle increase default row prefetch size
    # as default 10 is very low and slows down loading of all dimension members
    if @driver == 'oracle'
      prefetch_rows = @params[:prefetch_rows] || 100
      props.setProperty("jdbc.defaultRowPrefetch", prefetch_rows.to_s)
    end

    conn_string = connection_string

    # latest Mondrian version added ClassResolver which uses current thread class loader to load some classes
    # therefore need to set it to JRuby class loader to ensure that Mondrian classes are found
    # (e.g. when running mondrian-olap inside OSGi container)
    current_thread = Java::JavaLang::Thread.currentThread
    class_loader = current_thread.getContextClassLoader
    begin
      current_thread.setContextClassLoader JRuby.runtime.jruby_class_loader
      @raw_jdbc_connection = driver.connect(conn_string, props)
    ensure
      current_thread.setContextClassLoader(class_loader)
    end

    @raw_connection = @raw_jdbc_connection.unwrap(Java::OrgOlap4j::OlapConnection.java_class)
    @raw_catalog = @raw_connection.getOlapCatalog
    # currently it is assumed that there is just one schema per connection catalog
    @raw_schema = @raw_catalog.getSchemas.first
    @raw_mondrian_connection = @raw_connection.getMondrianConnection
    @raw_schema_reader = @raw_mondrian_connection.getSchemaReader
    @raw_cache_control = @raw_mondrian_connection.getCacheControl(nil)
    @connected = true
    true
  end
end
connected?() click to toggle source
# File lib/mondrian/olap/connection.rb, line 66
def connected?
  @connected
end
cube(name) click to toggle source
# File lib/mondrian/olap/connection.rb, line 144
def cube(name)
  Cube.get(self, name)
end
cube_names() click to toggle source
# File lib/mondrian/olap/connection.rb, line 140
def cube_names
  @raw_schema.getCubes.map{|c| c.getName}
end
execute(query_string, parameters = {}) click to toggle source
# File lib/mondrian/olap/connection.rb, line 79
def execute(query_string, parameters = {})
  options = {}
  Error.wrap_native_exception(options) do
    start_time = Time.now
    statement = @raw_connection.prepareOlapStatement(query_string)
    options[:profiling_statement] = statement if parameters[:profiling]
    set_statement_parameters(statement, parameters)
    raw_cell_set = statement.executeQuery()
    total_duration = ((Time.now - start_time) * 1000).to_i
    Result.new(self, raw_cell_set, profiling_handler: statement.getProfileHandler, total_duration: total_duration)
  end
end
execute_drill_through(query_string) click to toggle source
# File lib/mondrian/olap/connection.rb, line 99
def execute_drill_through(query_string)
  Error.wrap_native_exception do
    statement = @raw_connection.createStatement
    Result::DrillThrough.new(statement.executeQuery(query_string))
  end
end
flush_schema() click to toggle source

This method flushes the schema only for this connection (removes from the schema pool).

# File lib/mondrian/olap/connection.rb, line 168
def flush_schema
  if raw_mondrian_connection && (rolap_schema = raw_mondrian_connection.getSchema)
    raw_cache_control.flushSchema(rolap_schema)
  end
end
flush_schema_cache() click to toggle source

Will affect only the next created connection. If it is necessary to clear all schema cache then flush_schema_cache should be called, then close and then new connection should be created. This method flushes schemas for all connections (clears the schema pool).

# File lib/mondrian/olap/connection.rb, line 151
def flush_schema_cache
  raw_cache_control.flushSchemaCache
end
from(cube_name) click to toggle source
# File lib/mondrian/olap/connection.rb, line 106
def from(cube_name)
  Query.from(self, cube_name)
end
jdbc_uri() click to toggle source
# File lib/mondrian/olap/connection.rb, line 292
def jdbc_uri
  if respond_to?(method_name = "jdbc_uri_#{@driver}", true)
    send method_name
  else
    raise ArgumentError, 'unknown JDBC driver'
  end
end
locale() click to toggle source
# File lib/mondrian/olap/connection.rb, line 211
def locale
  @raw_connection.getLocale.toString
end
locale=(locale) click to toggle source
# File lib/mondrian/olap/connection.rb, line 215
def locale=(locale)
  locale_elements = locale.to_s.split('_')
  raise ArgumentError, "invalid locale string #{locale.inspect}" unless [1, 2, 3].include?(locale_elements.length)
  java_locale = Java::JavaUtil::Locale.new(*locale_elements)
  @raw_connection.setLocale(java_locale)
end
mondrian_parameter(parameter_name) click to toggle source

access mondrian.olap.Parameter object

# File lib/mondrian/olap/connection.rb, line 93
def mondrian_parameter(parameter_name)
  Error.wrap_native_exception do
    @raw_schema_reader.getParameter(parameter_name)
  end
end
mondrian_server() click to toggle source

access MondrianServer instance

# File lib/mondrian/olap/connection.rb, line 223
def mondrian_server
  Error.wrap_native_exception do
    @raw_connection.getMondrianConnection.getServer
  end
end
raw_schema_key() click to toggle source
# File lib/mondrian/olap/connection.rb, line 110
def raw_schema_key
  @raw_mondrian_connection.getSchema.getKey
end
role_name() click to toggle source
# File lib/mondrian/olap/connection.rb, line 185
def role_name
  @raw_connection.getRoleName
end
role_name=(name) click to toggle source
# File lib/mondrian/olap/connection.rb, line 195
def role_name=(name)
  Error.wrap_native_exception do
    @raw_connection.setRoleName(name)
  end
end
role_names() click to toggle source
# File lib/mondrian/olap/connection.rb, line 189
def role_names
  # workaround to access non-public method (was not public when using inside Torquebox)
  # @raw_connection.getRoleNames.to_a
  @raw_connection.java_method(:getRoleNames).call.to_a
end
role_names=(names) click to toggle source
# File lib/mondrian/olap/connection.rb, line 201
def role_names=(names)
  Error.wrap_native_exception do
    # workaround to access non-public method (was not public when using inside Torquebox)
    # @raw_connection.setRoleNames(Array(names))
    names = Array(names)
    @raw_connection.java_method(:setRoleNames, [Java::JavaUtil::List.java_class]).call(names)
    names
  end
end
schema_key() click to toggle source
# File lib/mondrian/olap/connection.rb, line 114
def schema_key
  raw_schema_key.toString
end

Private Instance Methods

catalog_content() click to toggle source
# File lib/mondrian/olap/connection.rb, line 445
def catalog_content
  if @params[:catalog_content]
    @params[:catalog_content]
  elsif @params[:schema]
    @params[:schema].to_xml(:driver => @driver)
  else
    raise ArgumentError, "Specify catalog with :catalog, :catalog_content or :schema option"
  end
end
catalog_uri() click to toggle source
# File lib/mondrian/olap/connection.rb, line 437
def catalog_uri
  if @params[:catalog]
    "file://#{File.expand_path(@params[:catalog])}"
  else
    raise ArgumentError, 'missing catalog source'
  end
end
connection_string() click to toggle source
# File lib/mondrian/olap/connection.rb, line 302
def connection_string
  string = "jdbc:mondrian:Jdbc=#{quote_string(jdbc_uri)};JdbcDrivers=#{jdbc_driver};"
  # by default use content checksum to reload schema when catalog has changed
  string += "UseContentChecksum=true;" unless @params[:use_content_checksum] == false
  string += "PinSchemaTimeout=#{@params[:pin_schema_timeout]};" if @params[:pin_schema_timeout]
  if role = @params[:role] || @params[:roles]
    roles = Array(role).map{|r| r && r.to_s.gsub(',', ',,')}.compact
    string += "Role=#{quote_string(roles.join(','))};" unless roles.empty?
  end
  if locale = @params[:locale]
    string += "Locale=#{quote_string(locale.to_s)};"
  end
  string + (@params[:catalog] ? "Catalog=#{catalog_uri}" : "CatalogContent=#{quote_string(catalog_content)}")
end
jdbc_driver() click to toggle source
# File lib/mondrian/olap/connection.rb, line 426
def jdbc_driver
  case @driver
  when 'mysql'
    (Java::com.mysql.cj.jdbc.Driver rescue nil) ? 'com.mysql.cj.jdbc.Driver' : 'com.mysql.jdbc.Driver'
  when 'jdbc'
    @params[:jdbc_driver] or raise ArgumentError, 'missing jdbc_driver parameter'
  else
    JDBC_DRIVER_CLASS[@driver] or raise ArgumentError, 'unknown JDBC driver'
  end
end
jdbc_uri_clickhouse() click to toggle source
# File lib/mondrian/olap/connection.rb, line 403
def jdbc_uri_clickhouse
  protocol_prefix = if protocol = @params[:protocol]
    raise ArgumentError, "invalid protocol #{protocol}" unless protocol =~ /\A\w+\z/
    ":#{protocol}"
  end
  uri_prefix = "jdbc:ch#{protocol_prefix}://"
  jdbc_uri_generic(uri_prefix: uri_prefix)
end
jdbc_uri_generic(options = {}) click to toggle source
# File lib/mondrian/olap/connection.rb, line 317
def jdbc_uri_generic(options = {})
  uri_prefix = options[:uri_prefix] || "jdbc:#{@driver}://"
  port = @params[:port] || options[:default_port]
  uri = "#{uri_prefix}#{@params[:host]}#{port && ":#{port}"}"
  uri += "/#{@params[:database]}" if @params[:database] && options[:add_database] != false
  properties = new_empty_properties
  properties.merge!(options[:default_properties]) if options[:default_properties].is_a?(Hash)
  properties.merge!(@params[:properties]) if @params[:properties].is_a?(Hash)
  "#{uri}#{uri_properties_string(properties, options[:separator], options[:first_separator])}"
end
jdbc_uri_jdbc() click to toggle source
# File lib/mondrian/olap/connection.rb, line 412
def jdbc_uri_jdbc
  @params[:jdbc_url] or raise ArgumentError, 'missing jdbc_url parameter'
end
jdbc_uri_mariadb(options = {})
Alias for: jdbc_uri_generic
jdbc_uri_mysql() click to toggle source
# File lib/mondrian/olap/connection.rb, line 341
def jdbc_uri_mysql
  jdbc_uri_generic(default_properties: {useUnicode: true, characterEncoding: 'UTF-8'})
end
jdbc_uri_oracle() click to toggle source
# File lib/mondrian/olap/connection.rb, line 349
def jdbc_uri_oracle
  # connection using TNS alias
  if @params[:database] && !@params[:host] && !@params[:url] && ENV['TNS_ADMIN']
    "jdbc:oracle:thin:@#{@params[:database]}"
  else
    @params[:url] || begin
      database = @params[:database]
      unless database =~ %r{^(:|/)}
        # assume database is a SID if no colon or slash are supplied (backward-compatibility)
        database = ":#{database}"
      end
      "jdbc:oracle:thin:@#{@params[:host] || 'localhost'}:#{@params[:port] || 1521}#{database}"
    end
  end
end
jdbc_uri_postgresql(options = {})
Alias for: jdbc_uri_generic
jdbc_uri_snowflake() click to toggle source
# File lib/mondrian/olap/connection.rb, line 396
def jdbc_uri_snowflake
  jdbc_uri_generic(
    add_database: false, separator: '&', first_separator: '/?',
    default_properties: uri_default_param_properties(JDBC_SNOWFLAKE_PARAM_PROPERTIES)
  )
end
jdbc_uri_sqlserver() click to toggle source
# File lib/mondrian/olap/connection.rb, line 373
def jdbc_uri_sqlserver
  jdbc_uri_generic(
    uri_prefix: 'jdbc:sqlserver://', add_database: false, separator: ';', first_separator: ';',
    default_properties: uri_default_param_properties(JDBC_SQLSERVER_PARAM_PROPERTIES)
  )
end
jdbc_uri_vertica(options = {})
Alias for: jdbc_uri_generic
new_empty_properties() click to toggle source
# File lib/mondrian/olap/connection.rb, line 328
def new_empty_properties
  # If ActiveSupport::HashWithIndifferentAccess is present then treat symbol and string keys as equal
  defined?(ActiveSupport::HashWithIndifferentAccess) ? ActiveSupport::HashWithIndifferentAccess.new : {}
end
quote_string(string) click to toggle source
# File lib/mondrian/olap/connection.rb, line 455
def quote_string(string)
  "'#{string.gsub("'", "''")}'"
end
set_statement_parameters(statement, parameters) click to toggle source
# File lib/mondrian/olap/connection.rb, line 459
def set_statement_parameters(statement, parameters)
  if parameters && !parameters.empty?
    parameters = parameters.dup
    # define addtional parameters which can be accessed from user defined functions
    if define_parameters = parameters.delete(:define_parameters)
      query_validator = statement.getQuery.createValidator
      define_parameters.each do |dp_name, dp_value|
        dp_type_class = dp_value.is_a?(Numeric) ? Java::MondrianOlapType::NumericType : Java::MondrianOlapType::StringType
        query_validator.createOrLookupParam(true, dp_name, dp_type_class.new, nil, nil)
        parameters[dp_name] = dp_value
      end
    end
    if parameters.delete(:profiling)
      statement.enableProfiling(ProfilingHandler.new)
    end
    if timeout = parameters.delete(:timeout)
      statement.getQuery.setQueryTimeoutMillis(timeout * 1000)
    end
    parameters.each do |parameter_name, value|
      statement.getQuery.setParameter(parameter_name, value)
    end
  end
end
uri_default_param_properties(param_properties) click to toggle source
# File lib/mondrian/olap/connection.rb, line 380
def uri_default_param_properties(param_properties)
  default_properties = {}
  param_properties.each do |key, property|
    if value = @params[key]
      default_properties[property] = value
    end
  end
  default_properties
end
uri_properties_string(properties, separator = nil, first_separator = nil) click to toggle source
# File lib/mondrian/olap/connection.rb, line 333
def uri_properties_string(properties, separator = nil, first_separator = nil)
  properties_string = properties.map { |k, v| "#{k}=#{v}" }.join(separator || '&')
  unless properties_string.empty?
    first_separator ||= '?'
    "#{first_separator}#{properties_string}"
  end
end