JDBC Databases offer a fairly uniform interface that does not change much based on the sub adapter.
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.
The type of database we are connecting to
The Java database driver we are using
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 184 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.
# File lib/sequel/adapters/jdbc.rb, line 200 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.
# File lib/sequel/adapters/jdbc.rb, line 230 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 unless e2.message == e.message e2.message << "\n#{e.class.name}: #{e.message}" end raise e2 end end end setup_connection(conn) end
Close given adapter connections, and delete any related prepared statements.
# File lib/sequel/adapters/jdbc.rb, line 266 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.
# File lib/sequel/adapters/jdbc.rb, line 273 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
Execute the given DDL SQL, which should not return any values or rows.
# File lib/sequel/adapters/jdbc.rb, line 298 def execute_ddl(sql, opts={}) execute(sql, {:type=>:ddl}.merge(opts)) end
Execute the given INSERT SQL, returning the last inserted row id.
# File lib/sequel/adapters/jdbc.rb, line 304 def execute_insert(sql, opts={}) execute(sql, {:type=>:insert}.merge(opts)) end
Use the JDBC metadata to get the index information for the table.
# File lib/sequel/adapters/jdbc.rb, line 309 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
Alias the generic JDBC versions so they can be called directly later
Whether or not JNDI is being used for this connection.
# File lib/sequel/adapters/jdbc.rb, line 327 def jndi? !!(uri =~ JNDI_URI_REGEXP) end
All tables in this database
# File lib/sequel/adapters/jdbc.rb, line 332 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.
# File lib/sequel/adapters/jdbc.rb, line 340 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
# File lib/sequel/adapters/jdbc.rb, line 347 def views(opts={}) get_tables('VIEW', opts) end
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 355 def cps_sync(conn, &block) @connection_prepared_statements_mutex.synchronize{yield(@connection_prepared_statements[conn] ||= {})} end
# File lib/sequel/adapters/jdbc.rb, line 359 def database_error_classes [NativeException] end
# File lib/sequel/adapters/jdbc.rb, line 363 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
Raise a disconnect error if the SQL state of the cause of the exception indicates so.
# File lib/sequel/adapters/jdbc.rb, line 379 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.
# File lib/sequel/adapters/jdbc.rb, line 391 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
# File lib/sequel/adapters/jdbc.rb, line 439 def execute_prepared_statement_insert(stmt) stmt.executeUpdate end
Execute the insert SQL using the statement
# File lib/sequel/adapters/jdbc.rb, line 444 def execute_statement_insert(stmt, sql) stmt.executeUpdate(sql) end
Gets the connection from JNDI.
# File lib/sequel/adapters/jdbc.rb, line 449 def get_connection_from_jndi jndi_name = JNDI_URI_REGEXP.match(uri)[1] JavaxNaming::InitialContext.new.lookup(jndi_name).connection end
Backbone of the tables and views support.
# File lib/sequel/adapters/jdbc.rb, line 463 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
Gets the JDBC connection uri from the JNDI resource.
# File lib/sequel/adapters/jdbc.rb, line 455 def get_uri_from_jndi conn = get_connection_from_jndi conn.meta_data.url ensure conn.close if conn end
Support Date objects used in bound variables
# File lib/sequel/adapters/jdbc.rb, line 471 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
# File lib/sequel/adapters/jdbc.rb, line 476 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
# File lib/sequel/adapters/jdbc.rb, line 483 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
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 499 def last_insert_id(conn, opts) nil end
Log the given SQL and then execute it on the connection, used by the transaction code.
# File lib/sequel/adapters/jdbc.rb, line 492 def log_connection_execute(conn, sql) statement(conn){|s| log_yield(sql){s.execute(sql)}} end
Yield the metadata for this database
# File lib/sequel/adapters/jdbc.rb, line 504 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
Parse the table schema for the given table.
# File lib/sequel/adapters/jdbc.rb, line 565 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.
# File lib/sequel/adapters/jdbc.rb, line 591 def schema_parse_table_skip?(h, schema) h[:table_schem] == 'INFORMATION_SCHEMA' 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.
# File lib/sequel/adapters/jdbc.rb, line 524 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.
# File lib/sequel/adapters/jdbc.rb, line 554 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.
# File lib/sequel/adapters/jdbc.rb, line 560 def setup_connection(conn) conn end
Yield a new statement object, and ensure that it is closed before returning.
# File lib/sequel/adapters/jdbc.rb, line 596 def statement(conn) stmt = conn.createStatement yield stmt rescue NativeException, JavaSQL::SQLException => e raise_error(e) ensure stmt.close if stmt end