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 Java database driver we are using (should be a Java class)
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 177 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 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 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 205 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 242 def disconnect_connection(c) @connection_prepared_statements_mutex.synchronize{@connection_prepared_statements.delete(c)} c.close end
# File lib/sequel/adapters/jdbc.rb, line 247 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 275 def execute_ddl(sql, opts=OPTS) opts = Hash[opts] opts[:type] = :ddl execute(sql, opts) end
# File lib/sequel/adapters/jdbc.rb, line 281 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 294 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 287 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 314 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 328 def jndi? !!(uri =~ JNDI_URI_REGEXP) end
All tables in this database
# File lib/sequel/adapters/jdbc.rb, line 333 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 341 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 348 def views(opts=OPTS) get_tables('VIEW', opts) end
Private Instance Methods
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 358 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 379 def cps_sync(conn, &block) @connection_prepared_statements_mutex.synchronize{yield(@connection_prepared_statements[conn] ||= {})} end
# File lib/sequel/adapters/jdbc.rb, line 383 def database_error_classes DATABASE_ERROR_CLASSES end
# File lib/sequel/adapters/jdbc.rb, line 387 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
# File lib/sequel/adapters/jdbc.rb, line 402 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 488 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 407 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 419 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 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)} 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 477 def execute_prepared_statement_insert(stmt) stmt.executeUpdate end
Execute the insert SQL using the statement
# File lib/sequel/adapters/jdbc.rb, line 482 def execute_statement_insert(stmt, sql) stmt.executeUpdate(sql) end
Gets the connection from JNDI.
# File lib/sequel/adapters/jdbc.rb, line 493 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 507 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 499 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 518 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 523 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 530 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 543 def last_insert_id(conn, opts) nil end
# File lib/sequel/adapters/jdbc.rb, line 536 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 548 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 560 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 622 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 635 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 674 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 578 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 608 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 613 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 618 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 679 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 684 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 690 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 701 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