class Sequel::JDBC::Database
Attributes
Map of JDBC type ids to callable objects that return appropriate ruby or java values.
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 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.
Map of JDBC type ids to callable objects that return appropriate ruby values.
Public Instance Methods
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 186 def call_sproc(name, opts = OPTS) args = opts[:args] || [] sql = "{call #{name}(#{args.map{'?'}.join(',')})}" synchronize(opts[:server]) do |conn| begin cps = conn.prepareCall(sql) i = 0 args.each{|arg| set_ps_arg(cps, arg, i+=1)} if defined?(yield) yield log_connection_yield(sql, conn){cps.executeQuery} else log_connection_yield(sql, conn){cps.executeUpdate} if opts[:type] == :insert last_insert_id(conn, opts) end end rescue *DATABASE_ERROR_CLASSES => e raise_error(e) ensure cps.close if cps end end end
Connect to the database using JavaSQL::DriverManager.getConnection, and falling back to driver.new.connect if the driver is known.
# File lib/sequel/adapters/jdbc.rb, line 214 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 StandardError, *DATABASE_ERROR_CLASSES => 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 StandardError, *DATABASE_ERROR_CLASSES => 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_with_opts(conn, opts) end
Close given adapter connections, and delete any related prepared statements.
# File lib/sequel/adapters/jdbc.rb, line 251 def disconnect_connection(c) @connection_prepared_statements_mutex.synchronize{@connection_prepared_statements.delete(c)} c.close end
# File lib/sequel/adapters/jdbc.rb, line 256 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)} opts = Hash[opts] opts[:stmt] = stmt last_insert_id(conn, opts) else log_connection_yield(sql, conn){stmt.executeUpdate(sql)} end end end end end
# File lib/sequel/adapters/jdbc.rb, line 284 def execute_ddl(sql, opts=OPTS) opts = Hash[opts] opts[:type] = :ddl execute(sql, opts) end
# File lib/sequel/adapters/jdbc.rb, line 290 def execute_insert(sql, opts=OPTS) opts = Hash[opts] opts[:type] = :insert execute(sql, opts) end
Use the JDBC metadata to get a list of foreign keys for the table.
# File lib/sequel/adapters/jdbc.rb, line 303 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
# File lib/sequel/adapters/jdbc.rb, line 296 def freeze @type_convertor_map.freeze @basic_type_convertor_map.freeze super end
Use the JDBC metadata to get the index information for the table.
# File lib/sequel/adapters/jdbc.rb, line 323 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
Whether or not JNDI is being used for this connection.
# File lib/sequel/adapters/jdbc.rb, line 337 def jndi? !!(uri =~ JNDI_URI_REGEXP) end
All tables in this database
# File lib/sequel/adapters/jdbc.rb, line 342 def tables(opts=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 350 def uri(opts=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 357 def views(opts=OPTS) get_tables('VIEW', opts) end
Private Instance Methods
# File lib/sequel/adapters/jdbc.rb, line 402 def _database_exception_sqlstate(exception, opts) 16.times do return exception.getSQLState if exception.respond_to?(:getSQLState) break unless exception.respond_to?(:cause) && (exception = exception.cause) end nil end
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 367 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
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 388 def cps_sync(conn, &block) @connection_prepared_statements_mutex.synchronize{yield(@connection_prepared_statements[conn] ||= {})} end
# File lib/sequel/adapters/jdbc.rb, line 392 def database_error_classes DATABASE_ERROR_CLASSES end
# File lib/sequel/adapters/jdbc.rb, line 396 def database_exception_sqlstate(exception, opts) if database_exception_use_sqlstates? _database_exception_sqlstate(exception, opts) end end
# File lib/sequel/adapters/jdbc.rb, line 416 def dataset_class_default Dataset end
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 501 def default_fetch_size 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 421 def disconnect_error?(exception, opts) super || (_database_exception_sqlstate(exception, opts) =~ /^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 432 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 if name opts = Hash[opts] opts[:name] = name end 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 defined?(yield) 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)} opts = Hash[opts] opts[:prepared] = true opts[:stmt] = cps last_insert_id(conn, opts) else log_connection_yield(msg, conn, args){cps.executeUpdate} end end rescue *DATABASE_ERROR_CLASSES => e raise_error(e) ensure cps.close unless name end end end
Execute the prepared insert statement
# File lib/sequel/adapters/jdbc.rb, line 490 def execute_prepared_statement_insert(stmt) stmt.executeUpdate end
Execute the insert SQL using the statement
# File lib/sequel/adapters/jdbc.rb, line 495 def execute_statement_insert(stmt, sql) stmt.executeUpdate(sql) end
Gets the connection from JNDI.
# File lib/sequel/adapters/jdbc.rb, line 506 def get_connection_from_jndi jndi_name = JNDI_URI_REGEXP.match(uri)[1] javax.naming.InitialContext.new.lookup(jndi_name).connection end
Backbone of the tables and views support.
# File lib/sequel/adapters/jdbc.rb, line 520 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
Gets the JDBC connection uri from the JNDI resource.
# File lib/sequel/adapters/jdbc.rb, line 512 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 531 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 536 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 * 1000000000).to_i) ts end
Support fractional seconds for Time objects used in bound variables
# File lib/sequel/adapters/jdbc.rb, line 543 def java_sql_timestamp(time) ts = java.sql.Timestamp.new(time.to_i * 1000) ts.setNanos(time.nsec) ts end
By default, there is no support for determining the last inserted id, so return nil. This method should be overridden in subadapters.
# File lib/sequel/adapters/jdbc.rb, line 556 def last_insert_id(conn, opts) nil end
# File lib/sequel/adapters/jdbc.rb, line 549 def log_connection_execute(conn, sql) statement(conn){|s| log_connection_yield(sql, conn){s.execute(sql)}} end
Yield the metadata for this database
# File lib/sequel/adapters/jdbc.rb, line 561 def metadata(*args, &block) synchronize do |c| result = c.getMetaData.public_send(*args) begin metadata_dataset.send(:process_result_set, result, &block) ensure result.close end end end
Return the schema and table suitable for use with metadata queries.
# File lib/sequel/adapters/jdbc.rb, line 573 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
# File lib/sequel/adapters/jdbc.rb, line 635 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
# File lib/sequel/adapters/jdbc.rb, line 648 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] =~ /number|numeric|decimal/i && 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
Skip tables in the INFORMATION_SCHEMA when parsing columns.
# File lib/sequel/adapters/jdbc.rb, line 687 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 591 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 621 def set_ps_arg_nil(cps, i) cps.setString(i, nil) end
Return the connection. Can be overridden in subadapters for database specific setup.
# File lib/sequel/adapters/jdbc.rb, line 626 def setup_connection(conn) conn end
Setup the connection using the given connection options. Return the connection. Can be overridden in subadapters for database specific setup.
# File lib/sequel/adapters/jdbc.rb, line 631 def setup_connection_with_opts(conn, opts) setup_connection(conn) end
Called after loading subadapter-specific code, overridable by subadapters.
# File lib/sequel/adapters/jdbc.rb, line 692 def setup_type_convertor_map end
Called before loading subadapter-specific code, necessary so that subadapter initialization code that runs queries works correctly. This cannot be overridden in subadapters.
# File lib/sequel/adapters/jdbc.rb, line 697 def setup_type_convertor_map_early @type_convertor_map = TypeConvertor::MAP.merge(Java::JavaSQL::Types::TIMESTAMP=>method(:timestamp_convert)) @basic_type_convertor_map = TypeConvertor::BASIC_MAP.dup end
Yield a new statement object, and ensure that it is closed before returning.
# File lib/sequel/adapters/jdbc.rb, line 703 def statement(conn) stmt = conn.createStatement yield stmt rescue *DATABASE_ERROR_CLASSES => e raise_error(e) ensure stmt.close if stmt end
A conversion method for timestamp columns. This is used to make sure timestamps are converted using the correct timezone.
# File lib/sequel/adapters/jdbc.rb, line 714 def timestamp_convert(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