class Sequel::JDBC::Database

JDBC Databases offer a fairly uniform interface that does not change much based on the sub adapter.

Constants

DatasetClass

Attributes

basic_type_convertor_map[R]

Map of JDBC type ids to callable objects that return appropriate ruby or java values.

convert_types[RW]

Whether to convert some Java types to ruby types when retrieving rows. True by default, can be set to false to roughly double performance when fetching rows.

database_type[R]

The type of database we are connecting to

driver[R]

The Java database driver we are using (should be a Java class)

fetch_size[RW]

The fetch size to use for JDBC Statement objects created by this database. By default, this is nil so a fetch size is not set explicitly.

type_convertor_map[R]

Map of JDBC type ids to callable objects that return appropriate ruby values.

Public Instance Methods

call_sproc(name, opts = OPTS) { |log_connection_yield(sql, conn){executeQuery}| ... } click to toggle source

Execute the given stored procedure with the give name. If a block is given, the stored procedure should return rows.

# File lib/sequel/adapters/jdbc.rb, line 174
def call_sproc(name, opts = OPTS)
  args = opts[:args] || []
  sql = "{call #{name}(#{args.map{'?'}.join(',')})}"
  synchronize(opts[:server]) do |conn|
    cps = conn.prepareCall(sql)

    i = 0
    args.each{|arg| set_ps_arg(cps, arg, i+=1)}

    begin
      if block_given?
        yield log_connection_yield(sql, conn){cps.executeQuery}
      else
        case opts[:type]
        when :insert
          log_connection_yield(sql, conn){cps.executeUpdate}
          last_insert_id(conn, opts)
        else
          log_connection_yield(sql, conn){cps.executeUpdate}
        end
      end
    rescue NativeException, JavaSQL::SQLException => e
      raise_error(e)
    ensure
      cps.close
    end
  end
end
connect(server) click to toggle source

Connect to the database using JavaSQL::DriverManager.getConnection.

# File lib/sequel/adapters/jdbc.rb, line 204
def connect(server)
  opts = server_opts(server)
  conn = if jndi?
    get_connection_from_jndi
  else
    args = [uri(opts)]
    args.concat([opts[:user], opts[:password]]) if opts[:user] && opts[:password]
    begin
      JavaSQL::DriverManager.setLoginTimeout(opts[:login_timeout]) if opts[:login_timeout]
      raise StandardError, "skipping regular connection" if opts[:jdbc_properties]
      JavaSQL::DriverManager.getConnection(*args)
    rescue JavaSQL::SQLException, NativeException, StandardError => e
      raise e unless driver
      # If the DriverManager can't get the connection - use the connect
      # method of the driver. (This happens under Tomcat for instance)
      props = java.util.Properties.new
      if opts && opts[:user] && opts[:password]
        props.setProperty("user", opts[:user])
        props.setProperty("password", opts[:password])
      end
      opts[:jdbc_properties].each{|k,v| props.setProperty(k.to_s, v)} if opts[:jdbc_properties]
      begin
        c = driver.new.connect(args[0], props)
        raise(Sequel::DatabaseError, 'driver.new.connect returned nil: probably bad JDBC connection string') unless c
        c
      rescue JavaSQL::SQLException, NativeException, StandardError => e2
        if e2.respond_to?(:message=) && e2.message != e.message
          e2.message = "#{e2.message}\n#{e.class.name}: #{e.message}"
        end
        raise e2
      end
    end
  end
  setup_connection(conn)
end
disconnect_connection(c) click to toggle source

Close given adapter connections, and delete any related prepared statements.

# File lib/sequel/adapters/jdbc.rb, line 241
def disconnect_connection(c)
  @connection_prepared_statements_mutex.synchronize{@connection_prepared_statements.delete(c)}
  c.close
end
execute(sql, opts=OPTS) { |log_connection_yield(sql, conn){executeQuery(sql)}| ... } click to toggle source

Execute the given SQL. If a block is given, if should be a SELECT statement or something else that returns rows.

# File lib/sequel/adapters/jdbc.rb, line 248
def execute(sql, opts=OPTS, &block)
  return call_sproc(sql, opts, &block) if opts[:sproc]
  return execute_prepared_statement(sql, opts, &block) if [Symbol, Dataset].any?{|c| sql.is_a?(c)}
  synchronize(opts[:server]) do |conn|
    statement(conn) do |stmt|
      if block
        if size = fetch_size
          stmt.setFetchSize(size)
        end
        yield log_connection_yield(sql, conn){stmt.executeQuery(sql)}
      else
        case opts[:type]
        when :ddl
          log_connection_yield(sql, conn){stmt.execute(sql)}
        when :insert
          log_connection_yield(sql, conn){execute_statement_insert(stmt, sql)}
          last_insert_id(conn, Hash[opts].merge!(:stmt=>stmt))
        else
          log_connection_yield(sql, conn){stmt.executeUpdate(sql)}
        end
      end
    end
  end
end
Also aliased as: execute_dui
execute_ddl(sql, opts=OPTS) click to toggle source

Execute the given DDL SQL, which should not return any values or rows.

# File lib/sequel/adapters/jdbc.rb, line 276
def execute_ddl(sql, opts=OPTS)
  opts = Hash[opts]
  opts[:type] = :ddl
  execute(sql, opts)
end
execute_dui(sql, opts=OPTS, &block)
Alias for: execute
execute_insert(sql, opts=OPTS) click to toggle source

Execute the given INSERT SQL, returning the last inserted row id.

# File lib/sequel/adapters/jdbc.rb, line 284
def execute_insert(sql, opts=OPTS)
  opts = Hash[opts]
  opts[:type] = :insert
  execute(sql, opts)
end
foreign_key_list(table, opts=OPTS) click to toggle source

Use the JDBC metadata to get a list of foreign keys for the table.

# File lib/sequel/adapters/jdbc.rb, line 297
def foreign_key_list(table, opts=OPTS)
  m = output_identifier_meth
  schema, table = metadata_schema_and_table(table, opts)
  foreign_keys = {}
  metadata(:getImportedKeys, nil, schema, table) do |r|
    if fk = foreign_keys[r[:fk_name]]
      fk[:columns] << [r[:key_seq], m.call(r[:fkcolumn_name])]
      fk[:key] << [r[:key_seq], m.call(r[:pkcolumn_name])]
    elsif r[:fk_name]
      foreign_keys[r[:fk_name]] = {:name=>m.call(r[:fk_name]), :columns=>[[r[:key_seq], m.call(r[:fkcolumn_name])]], :table=>m.call(r[:pktable_name]), :key=>[[r[:key_seq], m.call(r[:pkcolumn_name])]]}
    end
  end
  foreign_keys.values.each do |fk|
    [:columns, :key].each do |k|
      fk[k] = fk[k].sort.map{|_, v| v}
    end
  end
end
freeze() click to toggle source
Calls superclass method Sequel::Database#freeze
# File lib/sequel/adapters/jdbc.rb, line 290
def freeze
  @type_convertor_map.freeze
  @basic_type_convertor_map.freeze
  super
end
indexes(table, opts=OPTS) click to toggle source

Use the JDBC metadata to get the index information for the table.

# File lib/sequel/adapters/jdbc.rb, line 317
def indexes(table, opts=OPTS)
  m = output_identifier_meth
  schema, table = metadata_schema_and_table(table, opts)
  indexes = {}
  metadata(:getIndexInfo, nil, schema, table, false, true) do |r|
    next unless name = r[:column_name]
    next if respond_to?(:primary_key_index_re, true) and r[:index_name] =~ primary_key_index_re 
    i = indexes[m.call(r[:index_name])] ||= {:columns=>[], :unique=>[false, 0].include?(r[:non_unique])}
    i[:columns] << m.call(name)
  end
  indexes
end
Also aliased as: jdbc_indexes
jdbc_indexes(table, opts=OPTS)
Alias for: indexes
jdbc_schema_parse_table(table, opts=OPTS)

Alias the generic JDBC versions so they can be called directly later

Alias for: schema_parse_table
jdbc_tables(opts=OPTS)
Alias for: tables
jdbc_views(opts=OPTS)
Alias for: views
jndi?() click to toggle source

Whether or not JNDI is being used for this connection.

# File lib/sequel/adapters/jdbc.rb, line 331
def jndi?
  !!(uri =~ JNDI_URI_REGEXP)
end
tables(opts=OPTS) click to toggle source

All tables in this database

# File lib/sequel/adapters/jdbc.rb, line 336
def tables(opts=OPTS)
  get_tables('TABLE', opts)
end
Also aliased as: jdbc_tables
uri(opts=OPTS) click to toggle source

The uri for this connection. You can specify the uri using the :uri, :url, or :database options. You don't need to worry about this if you use Sequel.connect with the JDBC connectrion strings.

# File lib/sequel/adapters/jdbc.rb, line 344
def uri(opts=OPTS)
  opts = @opts.merge(opts)
  ur = opts[:uri] || opts[:url] || opts[:database]
  ur =~ /^\Ajdbc:/ ? ur : "jdbc:#{ur}"
end
views(opts=OPTS) click to toggle source

All views in this database

# File lib/sequel/adapters/jdbc.rb, line 351
def views(opts=OPTS)
  get_tables('VIEW', opts)
end
Also aliased as: jdbc_views

Private Instance Methods

adapter_initialize() click to toggle source

Call the DATABASE_SETUP proc directly after initialization, so the object always uses sub adapter specific code. Also, raise an error immediately if the connection doesn't have a uri, since JDBC requires one.

# File lib/sequel/adapters/jdbc.rb, line 361
def adapter_initialize
  @connection_prepared_statements = {}
  @connection_prepared_statements_mutex = Mutex.new
  @fetch_size = @opts[:fetch_size] ? typecast_value_integer(@opts[:fetch_size]) : default_fetch_size
  @convert_types = typecast_value_boolean(@opts.fetch(:convert_types, true))
  raise(Error, "No connection string specified") unless uri
  
  resolved_uri = jndi? ? get_uri_from_jndi : uri
  setup_type_convertor_map_early

  @driver = if (match = /\Ajdbc:([^:]+)/.match(resolved_uri)) && (prok = Sequel::Database.load_adapter(match[1].to_sym, :map=>DATABASE_SETUP, :subdir=>'jdbc'))
    prok.call(self)
  else
    @opts[:driver]
  end        

  setup_type_convertor_map
end
cps_sync(conn) { |connection_prepared_statements ||= {}| ... } click to toggle source

Yield the native prepared statements hash for the given connection to the block in a thread-safe manner.

# File lib/sequel/adapters/jdbc.rb, line 382
def cps_sync(conn, &block)
  @connection_prepared_statements_mutex.synchronize{yield(@connection_prepared_statements[conn] ||= {})}
end
database_error_classes() click to toggle source
# File lib/sequel/adapters/jdbc.rb, line 386
def database_error_classes
  [NativeException]
end
database_exception_sqlstate(exception, opts) click to toggle source
# File lib/sequel/adapters/jdbc.rb, line 390
def database_exception_sqlstate(exception, opts)
  if database_exception_use_sqlstates?
    while exception.respond_to?(:cause)
      exception = exception.cause
      return exception.getSQLState if exception.respond_to?(:getSQLState)
    end
  end
  nil
end
database_exception_use_sqlstates?() click to toggle source

Whether the JDBC subadapter should use SQL states for exception handling, true by default.

# File lib/sequel/adapters/jdbc.rb, line 401
def database_exception_use_sqlstates?
  true
end
default_fetch_size() click to toggle source

The default fetch size to use for statements. Nil by default, so that the default for the JDBC driver is used.

# File lib/sequel/adapters/jdbc.rb, line 480
def default_fetch_size
  nil
end
disconnect_error?(exception, opts) click to toggle source

Raise a disconnect error if the SQL state of the cause of the exception indicates so.

Calls superclass method Sequel::Database#disconnect_error?
# File lib/sequel/adapters/jdbc.rb, line 406
def disconnect_error?(exception, opts)
  cause = exception.respond_to?(:cause) ? exception.cause : exception
  super || (cause.respond_to?(:getSQLState) && cause.getSQLState =~ /^08/)
end
execute_prepared_statement(name, opts=OPTS) { |log_connection_yield(msg, conn, args){executeQuery}| ... } click to toggle source

Execute the prepared statement. If the provided name is a dataset, use that as the prepared statement, otherwise use it as a key to look it up in the prepared_statements hash. If the connection we are using has already prepared an identical statement, use that statement instead of creating another. Otherwise, prepare a new statement for the connection, bind the variables, and execute it.

# File lib/sequel/adapters/jdbc.rb, line 418
def execute_prepared_statement(name, opts=OPTS)
  args = opts[:arguments]
  if name.is_a?(Dataset)
    ps = name
    name = ps.prepared_statement_name
  else
    ps = prepared_statement(name)
  end
  sql = ps.prepared_sql
  synchronize(opts[:server]) do |conn|
    if name and cps = cps_sync(conn){|cpsh| cpsh[name]} and cps[0] == sql
      cps = cps[1]
    else
      log_connection_yield("CLOSE #{name}", conn){cps[1].close} if cps
      cps = log_connection_yield("PREPARE#{" #{name}:" if name} #{sql}", conn){prepare_jdbc_statement(conn, sql, opts)}
      if size = fetch_size
        cps.setFetchSize(size)
      end
      cps_sync(conn){|cpsh| cpsh[name] = [sql, cps]} if name
    end
    i = 0
    args.each{|arg| set_ps_arg(cps, arg, i+=1)}
    msg = "EXECUTE#{" #{name}" if name}"
    if ps.log_sql
      msg += " ("
      msg << sql
      msg << ")"
    end
    begin
      if block_given?
        yield log_connection_yield(msg, conn, args){cps.executeQuery}
      else
        case opts[:type]
        when :ddl
          log_connection_yield(msg, conn, args){cps.execute}
        when :insert
          log_connection_yield(msg, conn, args){execute_prepared_statement_insert(cps)}
          last_insert_id(conn, Hash[opts].merge!(:prepared=>true, :stmt=>cps))
        else
          log_connection_yield(msg, conn, args){cps.executeUpdate}
        end
      end
    rescue NativeException, JavaSQL::SQLException => e
      raise_error(e)
    ensure
      cps.close unless name
    end
  end
end
execute_prepared_statement_insert(stmt) click to toggle source

Execute the prepared insert statement

# File lib/sequel/adapters/jdbc.rb, line 469
def execute_prepared_statement_insert(stmt)
  stmt.executeUpdate
end
execute_statement_insert(stmt, sql) click to toggle source

Execute the insert SQL using the statement

# File lib/sequel/adapters/jdbc.rb, line 474
def execute_statement_insert(stmt, sql)
  stmt.executeUpdate(sql)
end
get_connection_from_jndi() click to toggle source

Gets the connection from JNDI.

# File lib/sequel/adapters/jdbc.rb, line 485
def get_connection_from_jndi
  jndi_name = JNDI_URI_REGEXP.match(uri)[1]
  JavaxNaming::InitialContext.new.lookup(jndi_name).connection
end
get_tables(type, opts) click to toggle source

Backbone of the tables and views support.

# File lib/sequel/adapters/jdbc.rb, line 499
def get_tables(type, opts)
  ts = []
  m = output_identifier_meth
  if schema = opts[:schema]
    schema = schema.to_s
  end
  metadata(:getTables, nil, schema, nil, [type].to_java(:string)){|h| ts << m.call(h[:table_name])}
  ts
end
get_uri_from_jndi() click to toggle source

Gets the JDBC connection uri from the JNDI resource.

# File lib/sequel/adapters/jdbc.rb, line 491
def get_uri_from_jndi
  conn = get_connection_from_jndi
  conn.meta_data.url
ensure
  conn.close if conn
end
java_sql_date(date) click to toggle source

Support Date objects used in bound variables

# File lib/sequel/adapters/jdbc.rb, line 510
def java_sql_date(date)
  java.sql.Date.new(Time.local(date.year, date.month, date.day).to_i * 1000)
end
java_sql_datetime(datetime) click to toggle source

Support DateTime objects used in bound variables

# File lib/sequel/adapters/jdbc.rb, line 515
def java_sql_datetime(datetime)
  ts = java.sql.Timestamp.new(Time.local(datetime.year, datetime.month, datetime.day, datetime.hour, datetime.min, datetime.sec).to_i * 1000)
  ts.setNanos((datetime.sec_fraction * (RUBY_VERSION >= '1.9.0' ?  1000000000 : 86400000000000)).to_i)
  ts
end
java_sql_timestamp(time) click to toggle source

Support fractional seconds for Time objects used in bound variables

# File lib/sequel/adapters/jdbc.rb, line 522
def java_sql_timestamp(time)
  ts = java.sql.Timestamp.new(time.to_i * 1000)
  # Work around jruby 1.6 ruby 1.9 mode bug
  ts.setNanos((RUBY_VERSION >= '1.9.0' && time.nsec != 0) ? time.nsec : time.usec * 1000)
  ts
end
last_insert_id(conn, opts) click to toggle source

By default, there is no support for determining the last inserted id, so return nil. This method should be overridden in sub adapters.

# File lib/sequel/adapters/jdbc.rb, line 538
def last_insert_id(conn, opts)
  nil
end
log_connection_execute(conn, sql) click to toggle source

Log the given SQL and then execute it on the connection, used by the transaction code.

# File lib/sequel/adapters/jdbc.rb, line 531
def log_connection_execute(conn, sql)
  statement(conn){|s| log_connection_yield(sql, conn){s.execute(sql)}}
end
metadata(*args, &block) click to toggle source

Yield the metadata for this database

# File lib/sequel/adapters/jdbc.rb, line 543
def metadata(*args, &block)
  synchronize do |c|
    result = c.getMetaData.send(*args)
    begin
      metadata_dataset.send(:process_result_set, result, &block)
    ensure
      result.close
    end
  end
end
metadata_schema_and_table(table, opts) click to toggle source

Return the schema and table suitable for use with metadata queries.

# File lib/sequel/adapters/jdbc.rb, line 555
def metadata_schema_and_table(table, opts)
  im = input_identifier_meth(opts[:dataset])
  schema, table = schema_and_table(table)
  schema ||= opts[:schema]
  schema = im.call(schema) if schema
  table = im.call(table)
  [schema, table]
end
prepare_jdbc_statement(conn, sql, opts) click to toggle source

Created a JDBC prepared statement on the connection with the given SQL.

# File lib/sequel/adapters/jdbc.rb, line 565
def prepare_jdbc_statement(conn, sql, opts)
  conn.prepareStatement(sql)
end
schema_column_set_db_type(schema) click to toggle source
# File lib/sequel/adapters/jdbc.rb, line 613
def schema_column_set_db_type(schema)
  case schema[:type]
  when :string
    if schema[:db_type] =~ /\A(character( varying)?|n?(var)?char2?)\z/io && schema[:column_size] > 0
      schema[:db_type] += "(#{schema[:column_size]})"
    end
  when :decimal
    if schema[:db_type] =~ /\A(decimal|numeric)\z/io && schema[:column_size] > 0 && schema[:scale] >= 0
      schema[:db_type] += "(#{schema[:column_size]}, #{schema[:scale]})"
    end
  end
end
schema_parse_table(table, opts=OPTS) click to toggle source

Parse the table schema for the given table.

# File lib/sequel/adapters/jdbc.rb, line 627
def schema_parse_table(table, opts=OPTS)
  m = output_identifier_meth(opts[:dataset])
  schema, table = metadata_schema_and_table(table, opts)
  pks, ts = [], []
  metadata(:getPrimaryKeys, nil, schema, table) do |h|
    next if schema_parse_table_skip?(h, schema)
    pks << h[:column_name]
  end
  schemas = []
  metadata(:getColumns, nil, schema, table, nil) do |h|
    next if schema_parse_table_skip?(h, schema)
    s = {
      :type=>schema_column_type(h[:type_name]),
      :db_type=>h[:type_name],
      :default=>(h[:column_def] == '' ? nil : h[:column_def]),
      :allow_null=>(h[:nullable] != 0),
      :primary_key=>pks.include?(h[:column_name]),
      :column_size=>h[:column_size],
      :scale=>h[:decimal_digits],
      :remarks=>h[:remarks]
    }
    if s[:primary_key]
      s[:auto_increment] = h[:is_autoincrement] == "YES"
    end
    s[:max_length] = s[:column_size] if s[:type] == :string
    if s[:db_type] =~ DECIMAL_TYPE_RE && s[:scale] == 0
      s[:type] = :integer
    end
    schema_column_set_db_type(s)
    schemas << h[:table_schem] unless schemas.include?(h[:table_schem])
    ts << [m.call(h[:column_name]), s]
  end
  if schemas.length > 1
    raise Error, 'Schema parsing in the jdbc adapter resulted in columns being returned for a table with the same name in multiple schemas.  Please explicitly qualify your table with a schema.'
  end
  ts
end
Also aliased as: jdbc_schema_parse_table
schema_parse_table_skip?(h, schema) click to toggle source

Whether #schema_parse_table should skip the given row when parsing the schema.

# File lib/sequel/adapters/jdbc.rb, line 667
def schema_parse_table_skip?(h, schema)
  h[:table_schem] == 'INFORMATION_SCHEMA'
end
set_ps_arg(cps, arg, i) click to toggle source

Java being java, you need to specify the type of each argument for the prepared statement, and bind it individually. This guesses which JDBC method to use, and hopefully JRuby will convert things properly for us.

# File lib/sequel/adapters/jdbc.rb, line 573
def set_ps_arg(cps, arg, i)
  case arg
  when Integer
    cps.setLong(i, arg)
  when Sequel::SQL::Blob
    cps.setBytes(i, arg.to_java_bytes)
  when String
    cps.setString(i, arg)
  when Float
    cps.setDouble(i, arg)
  when TrueClass, FalseClass
    cps.setBoolean(i, arg)
  when NilClass
    set_ps_arg_nil(cps, i)
  when DateTime
    cps.setTimestamp(i, java_sql_datetime(arg))
  when Date
    cps.setDate(i, java_sql_date(arg))
  when Time
    cps.setTimestamp(i, java_sql_timestamp(arg))
  when Java::JavaSql::Timestamp
    cps.setTimestamp(i, arg)
  when Java::JavaSql::Date
    cps.setDate(i, arg)
  else
    cps.setObject(i, arg)
  end
end
set_ps_arg_nil(cps, i) click to toggle source

Use setString with a nil value by default, but this doesn't work on all subadapters.

# File lib/sequel/adapters/jdbc.rb, line 603
def set_ps_arg_nil(cps, i)
  cps.setString(i, nil)
end
setup_connection(conn) click to toggle source

Return the connection. Used to do configuration on the connection object before adding it to the connection pool.

# File lib/sequel/adapters/jdbc.rb, line 609
def setup_connection(conn)
  conn
end
setup_type_convertor_map() click to toggle source

Called after loading subadapter-specific code, overridable by subadapters.

# File lib/sequel/adapters/jdbc.rb, line 672
def setup_type_convertor_map
end
setup_type_convertor_map_early() click to toggle source

Called before loading subadapter-specific code, necessary so that subadapter initialization code that runs queries works correctly. This cannot be overriding in subadapters,

# File lib/sequel/adapters/jdbc.rb, line 677
def setup_type_convertor_map_early
  @type_convertor_map = TypeConvertor::MAP.merge(Java::JavaSQL::Types::TIMESTAMP=>timestamp_convertor)
  @basic_type_convertor_map = TypeConvertor::BASIC_MAP.dup
end
statement(conn) { |stmt| ... } click to toggle source

Yield a new statement object, and ensure that it is closed before returning.

# File lib/sequel/adapters/jdbc.rb, line 683
def statement(conn)
  stmt = conn.createStatement
  yield stmt
rescue NativeException, JavaSQL::SQLException => e
  raise_error(e)
ensure
  stmt.close if stmt
end
timestamp_convertor() click to toggle source

A conversion proc for timestamp columns. This is used to make sure timestamps are converted using the correct timezone.

# File lib/sequel/adapters/jdbc.rb, line 694
def timestamp_convertor
  lambda do |r, i|
    if v = r.getTimestamp(i)
      to_application_timestamp([v.getYear + 1900, v.getMonth + 1, v.getDate, v.getHours, v.getMinutes, v.getSeconds, v.getNanos])
    end
  end
end