require 'java'
Sequel.require 'adapters/utils/stored_procedures'
module Sequel
# Houses Sequel's JDBC support when running on JRuby.
module JDBC
# Make it accesing the java.lang hierarchy more ruby friendly.
module JavaLang
include_package 'java.lang'
end
# Make it accesing the java.sql hierarchy more ruby friendly.
module JavaSQL
include_package 'java.sql'
end
# Make it accesing the javax.naming hierarchy more ruby friendly.
module JavaxNaming
include_package 'javax.naming'
end
# Used to identify a jndi connection and to extract the jndi
# resource name.
JNDI_URI_REGEXP = /\Ajdbc:jndi:(.+)/
# The types to check for 0 scale to transform :decimal types
# to :integer.
DECIMAL_TYPE_RE = /number|numeric|decimal/io
# Contains procs keyed on sub adapter type that extend the
# given database object so it supports the correct database type.
DATABASE_SETUP = {:postgresql=>proc do |db|
Sequel.ts_require 'adapters/jdbc/postgresql'
db.extend(Sequel::JDBC::Postgres::DatabaseMethods)
db.dataset_class = Sequel::JDBC::Postgres::Dataset
JDBC.load_gem('postgres')
org.postgresql.Driver
end,
:mysql=>proc do |db|
Sequel.ts_require 'adapters/jdbc/mysql'
db.extend(Sequel::JDBC::MySQL::DatabaseMethods)
db.extend_datasets Sequel::MySQL::DatasetMethods
JDBC.load_gem('mysql')
com.mysql.jdbc.Driver
end,
:sqlite=>proc do |db|
Sequel.ts_require 'adapters/jdbc/sqlite'
db.extend(Sequel::JDBC::SQLite::DatabaseMethods)
db.extend_datasets Sequel::SQLite::DatasetMethods
db.set_integer_booleans
JDBC.load_gem('sqlite3')
org.sqlite.JDBC
end,
:oracle=>proc do |db|
Sequel.ts_require 'adapters/jdbc/oracle'
db.extend(Sequel::JDBC::Oracle::DatabaseMethods)
db.dataset_class = Sequel::JDBC::Oracle::Dataset
Java::oracle.jdbc.driver.OracleDriver
end,
:sqlserver=>proc do |db|
Sequel.ts_require 'adapters/jdbc/sqlserver'
db.extend(Sequel::JDBC::SQLServer::DatabaseMethods)
db.extend_datasets Sequel::MSSQL::DatasetMethods
db.send(:set_mssql_unicode_strings)
com.microsoft.sqlserver.jdbc.SQLServerDriver
end,
:jtds=>proc do |db|
Sequel.ts_require 'adapters/jdbc/jtds'
db.extend(Sequel::JDBC::JTDS::DatabaseMethods)
db.dataset_class = Sequel::JDBC::JTDS::Dataset
db.send(:set_mssql_unicode_strings)
JDBC.load_gem('jtds')
Java::net.sourceforge.jtds.jdbc.Driver
end,
:h2=>proc do |db|
Sequel.ts_require 'adapters/jdbc/h2'
db.extend(Sequel::JDBC::H2::DatabaseMethods)
db.dataset_class = Sequel::JDBC::H2::Dataset
JDBC.load_gem('h2')
org.h2.Driver
end,
:hsqldb=>proc do |db|
Sequel.ts_require 'adapters/jdbc/hsqldb'
db.extend(Sequel::JDBC::HSQLDB::DatabaseMethods)
db.dataset_class = Sequel::JDBC::HSQLDB::Dataset
# Current gem is 1.8.1.3, but Sequel supports 2.2.5
org.hsqldb.jdbcDriver
end,
:derby=>proc do |db|
Sequel.ts_require 'adapters/jdbc/derby'
db.extend(Sequel::JDBC::Derby::DatabaseMethods)
db.dataset_class = Sequel::JDBC::Derby::Dataset
JDBC.load_gem('derby')
org.apache.derby.jdbc.EmbeddedDriver
end,
:as400=>proc do |db|
Sequel.ts_require 'adapters/jdbc/as400'
db.extend(Sequel::JDBC::AS400::DatabaseMethods)
db.dataset_class = Sequel::JDBC::AS400::Dataset
com.ibm.as400.access.AS400JDBCDriver
end,
:"informix-sqli"=>proc do |db|
Sequel.ts_require 'adapters/jdbc/informix'
db.extend(Sequel::JDBC::Informix::DatabaseMethods)
db.extend_datasets Sequel::Informix::DatasetMethods
com.informix.jdbc.IfxDriver
end,
:db2=>proc do |db|
Sequel.ts_require 'adapters/jdbc/db2'
db.extend(Sequel::JDBC::DB2::DatabaseMethods)
db.dataset_class = Sequel::JDBC::DB2::Dataset
com.ibm.db2.jcc.DB2Driver
end,
:firebirdsql=>proc do |db|
Sequel.ts_require 'adapters/jdbc/firebird'
db.extend(Sequel::JDBC::Firebird::DatabaseMethods)
db.extend_datasets Sequel::Firebird::DatasetMethods
org.firebirdsql.jdbc.FBDriver
end,
:jdbcprogress=>proc do |db|
Sequel.ts_require 'adapters/jdbc/progress'
db.extend(Sequel::JDBC::Progress::DatabaseMethods)
db.extend_datasets Sequel::Progress::DatasetMethods
com.progress.sql.jdbc.JdbcProgressDriver
end,
:cubrid=>proc do |db|
Sequel.ts_require 'adapters/jdbc/cubrid'
db.extend(Sequel::JDBC::Cubrid::DatabaseMethods)
db.extend_datasets Sequel::Cubrid::DatasetMethods
Java::cubrid.jdbc.driver.CUBRIDDriver
end
}
# Allowing loading the necessary JDBC support via a gem, which
# works for PostgreSQL, MySQL, and SQLite.
def self.load_gem(name)
begin
Sequel.tsk_require "jdbc/#{name}"
rescue LoadError
# jdbc gem not used, hopefully the user has the .jar in their CLASSPATH
end
end
# JDBC Databases offer a fairly uniform interface that does not change
# much based on the sub adapter.
class Database < Sequel::Database
set_adapter_scheme :jdbc
# The type of database we are connecting to
attr_reader :database_type
# The Java database driver we are using
attr_reader :driver
# 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.
attr_accessor :convert_types
# 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.
def initialize(opts)
super
@connection_prepared_statements = {}
@connection_prepared_statements_mutex = Mutex.new
@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
if match = /\Ajdbc:([^:]+)/.match(resolved_uri) and prok = DATABASE_SETUP[match[1].to_sym]
@driver = prok.call(self)
end
end
# Execute the given stored procedure with the give name. If a block is
# given, the stored procedure should return rows.
def call_sproc(name, 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_yield(sql){cps.executeQuery}
else
case opts[:type]
when :insert
log_yield(sql){cps.executeUpdate}
last_insert_id(conn, opts)
else
log_yield(sql){cps.executeUpdate}
end
end
rescue NativeException, JavaSQL::SQLException => e
raise_error(e)
ensure
cps.close
end
end
end
# Connect to the database using JavaSQL::DriverManager.getConnection.
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]
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
e.message << "\n#{e2.class.name}: #{e2.message}"
raise e
end
end
end
setup_connection(conn)
end
# Close given adapter connections, and delete any related prepared statements.
def disconnect_connection(c)
@connection_prepared_statements_mutex.synchronize{@connection_prepared_statements.delete(c)}
c.close
end
# Execute the given SQL. If a block is given, if should be a SELECT
# statement or something else that returns rows.
def execute(sql, 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
yield log_yield(sql){stmt.executeQuery(sql)}
else
case opts[:type]
when :ddl
log_yield(sql){stmt.execute(sql)}
when :insert
log_yield(sql){execute_statement_insert(stmt, sql)}
last_insert_id(conn, opts.merge(:stmt=>stmt))
else
log_yield(sql){stmt.executeUpdate(sql)}
end
end
end
end
end
alias execute_dui execute
# Execute the given DDL SQL, which should not return any
# values or rows.
def execute_ddl(sql, opts={})
execute(sql, {:type=>:ddl}.merge(opts))
end
# Execute the given INSERT SQL, returning the last inserted
# row id.
def execute_insert(sql, opts={})
execute(sql, {:type=>:insert}.merge(opts))
end
# Use the JDBC metadata to get the index information for the table.
def indexes(table, opts={})
m = output_identifier_meth
im = input_identifier_meth
schema, table = schema_and_table(table)
schema ||= opts[:schema]
schema = im.call(schema) if schema
table = im.call(table)
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
# Whether or not JNDI is being used for this connection.
def jndi?
!!(uri =~ JNDI_URI_REGEXP)
end
# All tables in this database
def tables(opts={})
get_tables('TABLE', opts)
end
# 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.
def uri(opts={})
opts = @opts.merge(opts)
ur = opts[:uri] || opts[:url] || opts[:database]
ur =~ /^\Ajdbc:/ ? ur : "jdbc:#{ur}"
end
# All views in this database
def views(opts={})
get_tables('VIEW', opts)
end
private
# Yield the native prepared statements hash for the given connection
# to the block in a thread-safe manner.
def cps_sync(conn, &block)
@connection_prepared_statements_mutex.synchronize{yield(@connection_prepared_statements[conn] ||= {})}
end
def database_error_classes
[NativeException]
end
# Raise a disconnect error if the SQL state of the cause of the exception indicates so.
def disconnect_error?(exception, opts)
cause = exception.respond_to?(:cause) ? exception.cause : exception
super || (cause.respond_to?(:getSQLState) && cause.getSQLState =~ /^08/)
end
# 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.
def execute_prepared_statement(name, opts={})
args = opts[:arguments]
if Dataset === name
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_yield("CLOSE #{name}"){cps[1].close} if cps
cps = log_yield("PREPARE#{" #{name}:" if name} #{sql}"){prepare_jdbc_statement(conn, sql, opts)}
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_yield(msg, args){cps.executeQuery}
else
case opts[:type]
when :ddl
log_yield(msg, args){cps.execute}
when :insert
log_yield(msg, args){execute_prepared_statement_insert(cps)}
last_insert_id(conn, opts.merge(:prepared=>true, :stmt=>cps))
else
log_yield(msg, args){cps.executeUpdate}
end
end
rescue NativeException, JavaSQL::SQLException => e
raise_error(e)
ensure
cps.close unless name
end
end
end
# Execute the prepared insert statement
def execute_prepared_statement_insert(stmt)
stmt.executeUpdate
end
# Execute the insert SQL using the statement
def execute_statement_insert(stmt, sql)
stmt.executeUpdate(sql)
end
# Gets the connection from JNDI.
def get_connection_from_jndi
jndi_name = JNDI_URI_REGEXP.match(uri)[1]
JavaxNaming::InitialContext.new.lookup(jndi_name).connection
end
# Gets the JDBC connection uri from the JNDI resource.
def get_uri_from_jndi
conn = get_connection_from_jndi
conn.meta_data.url
ensure
conn.close if conn
end
# Backbone of the tables and views support.
def get_tables(type, opts)
ts = []
m = output_identifier_meth
metadata(:getTables, nil, nil, nil, [type].to_java(:string)){|h| ts << m.call(h[:table_name])}
ts
end
# Support Date objects used in bound variables
def java_sql_date(date)
java.sql.Date.new(Time.local(date.year, date.month, date.day).to_i * 1000)
end
# Support DateTime objects used in bound variables
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
# Support fractional seconds for Time objects used in bound variables
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
# Log the given SQL and then execute it on the connection, used by
# the transaction code.
def log_connection_execute(conn, sql)
statement(conn){|s| log_yield(sql){s.execute(sql)}}
end
# By default, there is no support for determining the last inserted
# id, so return nil. This method should be overridden in
# sub adapters.
def last_insert_id(conn, opts)
nil
end
# Yield the metadata for this database
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
# Created a JDBC prepared statement on the connection with the given SQL.
def prepare_jdbc_statement(conn, sql, opts)
conn.prepareStatement(sql)
end
# 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.
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
# Use setString with a nil value by default, but this doesn't work on all subadapters.
def set_ps_arg_nil(cps, i)
cps.setString(i, nil)
end
# Return the connection. Used to do configuration on the
# connection object before adding it to the connection pool.
def setup_connection(conn)
conn
end
# Parse the table schema for the given table.
def schema_parse_table(table, opts={})
m = output_identifier_meth(opts[:dataset])
im = input_identifier_meth(opts[:dataset])
ds = dataset
schema, table = schema_and_table(table)
schema ||= opts[:schema]
schema = im.call(schema) if schema
table = im.call(table)
pks, ts = [], []
metadata(:getPrimaryKeys, nil, schema, table) do |h|
next if schema_parse_table_skip?(h, schema)
pks << h[:column_name]
end
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]}
if s[:db_type] =~ DECIMAL_TYPE_RE && s[:scale] == 0
s[:type] = :integer
end
ts << [m.call(h[:column_name]), s]
end
ts
end
# Whether schema_parse_table should skip the given row when
# parsing the schema.
def schema_parse_table_skip?(h, schema)
h[:table_schem] == 'INFORMATION_SCHEMA'
end
# Yield a new statement object, and ensure that it is closed before returning.
def statement(conn)
stmt = conn.createStatement
yield stmt
rescue NativeException, JavaSQL::SQLException => e
raise_error(e)
ensure
stmt.close if stmt
end
end
class Dataset < Sequel::Dataset
include StoredProcedures
Database::DatasetClass = self
# Use JDBC PreparedStatements instead of emulated ones. Statements
# created using #prepare are cached at the connection level to allow
# reuse. This also supports bind variables by using unnamed
# prepared statements created using #call.
module PreparedStatementMethods
include Sequel::Dataset::UnnumberedArgumentMapper
private
# Execute the prepared SQL using the stored type and
# arguments derived from the hash passed to call.
def execute(sql, opts={}, &block)
super(self, {:arguments=>bind_arguments}.merge(opts), &block)
end
# Same as execute, explicit due to intricacies of alias and super.
def execute_dui(sql, opts={}, &block)
super(self, {:arguments=>bind_arguments}.merge(opts), &block)
end
# Same as execute, explicit due to intricacies of alias and super.
def execute_insert(sql, opts={}, &block)
super(self, {:arguments=>bind_arguments, :type=>:insert}.merge(opts), &block)
end
end
# Use JDBC CallableStatements to execute stored procedures. Only supported
# if the underlying database has stored procedure support.
module StoredProcedureMethods
include Sequel::Dataset::StoredProcedureMethods
private
# Execute the database stored procedure with the stored arguments.
def execute(sql, opts={}, &block)
super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
end
# Same as execute, explicit due to intricacies of alias and super.
def execute_dui(sql, opts={}, &block)
super(@sproc_name, {:args=>@sproc_args, :sproc=>true}.merge(opts), &block)
end
# Same as execute, explicit due to intricacies of alias and super.
def execute_insert(sql, opts={}, &block)
super(@sproc_name, {:args=>@sproc_args, :sproc=>true, :type=>:insert}.merge(opts), &block)
end
end
# Whether to convert some Java types to ruby types when retrieving rows.
# Uses the database's setting by default, can be set to false to roughly
# double performance when fetching rows.
attr_accessor :convert_types
# Correctly return rows from the database and return them as hashes.
def fetch_rows(sql, &block)
execute(sql){|result| process_result_set(result, &block)}
self
end
# Create a named prepared statement that is stored in the
# database (and connection) for reuse.
def prepare(type, name=nil, *values)
ps = to_prepared_statement(type, values)
ps.extend(PreparedStatementMethods)
if name
ps.prepared_statement_name = name
db.set_prepared_statement(name, ps)
end
ps
end
private
# Cache Java class constants to speed up lookups
JAVA_SQL_TIMESTAMP = Java::JavaSQL::Timestamp
JAVA_SQL_TIME = Java::JavaSQL::Time
JAVA_SQL_DATE = Java::JavaSQL::Date
JAVA_SQL_BLOB = Java::JavaSQL::Blob
JAVA_SQL_CLOB = Java::JavaSQL::Clob
JAVA_BUFFERED_READER = Java::JavaIo::BufferedReader
JAVA_BIG_DECIMAL = Java::JavaMath::BigDecimal
JAVA_BYTE_ARRAY = Java::byte[]
JAVA_UUID = Java::JavaUtil::UUID
# Handle type conversions for common Java types.
class TYPE_TRANSLATOR
LF = "\n".freeze
def time(v) Sequel.string_to_time("#{v.to_string}.#{sprintf('%03i', v.getTime.divmod(1000).last)}") end
def date(v) Date.civil(v.getYear + 1900, v.getMonth + 1, v.getDate) end
def decimal(v) BigDecimal.new(v.to_string) end
def byte_array(v) Sequel::SQL::Blob.new(String.from_java_bytes(v)) end
def blob(v) Sequel::SQL::Blob.new(String.from_java_bytes(v.getBytes(1, v.length))) end
def clob(v) v.getSubString(1, v.length) end
def buffered_reader(v)
lines = ""
c = false
while(line = v.read_line) do
lines << LF if c
lines << line
c ||= true
end
lines
end
def uuid(v) v.to_string end
end
TYPE_TRANSLATOR_INSTANCE = tt = TYPE_TRANSLATOR.new
# Cache type translator methods so that duplicate Method
# objects are not created.
DECIMAL_METHOD = tt.method(:decimal)
TIME_METHOD = tt.method(:time)
DATE_METHOD = tt.method(:date)
BUFFERED_READER_METHOD = tt.method(:buffered_reader)
BYTE_ARRAY_METHOD = tt.method(:byte_array)
BLOB_METHOD = tt.method(:blob)
CLOB_METHOD = tt.method(:clob)
UUID_METHOD = tt.method(:uuid)
# Convert the given Java timestamp to an instance of Sequel.datetime_class.
def convert_type_timestamp(v)
db.to_application_timestamp([v.getYear + 1900, v.getMonth + 1, v.getDate, v.getHours, v.getMinutes, v.getSeconds, v.getNanos])
end
# Return a callable object that will convert any value of v's
# class to a ruby object. If no callable object can handle v's
# class, return false so that the negative lookup is cached.
def convert_type_proc(v)
case v
when JAVA_BIG_DECIMAL
DECIMAL_METHOD
when JAVA_SQL_TIMESTAMP
method(:convert_type_timestamp)
when JAVA_SQL_TIME
TIME_METHOD
when JAVA_SQL_DATE
DATE_METHOD
when JAVA_BUFFERED_READER
BUFFERED_READER_METHOD
when JAVA_BYTE_ARRAY
BYTE_ARRAY_METHOD
when JAVA_SQL_BLOB
BLOB_METHOD
when JAVA_SQL_CLOB
CLOB_METHOD
when JAVA_UUID
UUID_METHOD
else
false
end
end
# Extend the dataset with the JDBC stored procedure methods.
def prepare_extend_sproc(ds)
ds.extend(StoredProcedureMethods)
end
# Split out from fetch rows to allow processing of JDBC result sets
# that don't come from issuing an SQL string.
def process_result_set(result, &block)
# get column names
meta = result.getMetaData
cols = []
i = 0
meta.getColumnCount.times{cols << [output_identifier(meta.getColumnLabel(i+=1)), i]}
columns = cols.map{|c| c.at(0)}
if opts[:offset] && offset_returns_row_number_column?
rn = row_number_column
columns.delete(rn)
end
@columns = columns
ct = @convert_types
if (ct.nil? ? db.convert_types : ct)
cols.each{|c| c << nil}
process_result_set_convert(cols, result, rn, &block)
else
process_result_set_no_convert(cols, result, rn, &block)
end
ensure
result.close
end
# Use conversion procs to convert data retrieved
# from the database. This has been optimized, the algorithm it uses
# is roughly, for each column value in each row:
# * check if the value is truthy (not false/nil)
# * if not truthy, return object
# * otherwise, see if a conversion method exists for
# the column. All columns start with a nil conversion proc,
# since unlike other adapters, Sequel doesn't get the type of
# the column when parsing the column metadata.
# * if a conversion proc is not false/nil, call it with the object
# and return the result.
# * if a conversion proc has already been looked up and doesn't
# exist (false value), return object.
# * if a conversion proc hasn't been looked up yet (nil value),
# call convert_type_proc to get the conversion method. Cache
# the result of as the column's conversion proc to speed up
# later processing. If the conversion proc exists, call it
# and return the result, otherwise, return the object.
def process_result_set_convert(cols, result, rn)
while result.next
row = {}
cols.each do |n, i, p|
v = result.getObject(i)
row[n] = if v
if p
p.call(v)
elsif p.nil?
cols[i-1][2] = p = convert_type_proc(v)
if p
p.call(v)
else
v
end
else
v
end
else
v
end
end
row.delete(rn) if rn
yield row
end
end
# Yield rows without calling any conversion procs. This
# may yield Java values and not ruby values.
def process_result_set_no_convert(cols, result, rn)
while result.next
row = {}
cols.each{|n, i| row[n] = result.getObject(i)}
row.delete(rn) if rn
yield row
end
end
end
end
end