class Sequel::JDBC::Database
JDBC Databases offer a fairly uniform interface that does not change much based on the sub adapter.
Constants
- DatasetClass
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 type of database we are connecting to
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 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 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
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 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
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 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
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
# File lib/sequel/adapters/jdbc.rb, line 290 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 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
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 331 def jndi? !!(uri =~ JNDI_URI_REGEXP) end
All tables in this database
# File lib/sequel/adapters/jdbc.rb, line 336 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 344 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 351 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 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
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
# File lib/sequel/adapters/jdbc.rb, line 386 def database_error_classes [NativeException] end
# 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
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
Raise a disconnect error if the SQL state of the cause of the exception indicates so.
# 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 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 the prepared insert statement
# File lib/sequel/adapters/jdbc.rb, line 469 def execute_prepared_statement_insert(stmt) stmt.executeUpdate end
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
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
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
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
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
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
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
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 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
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
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
# 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
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
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
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
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
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
Called after loading subadapter-specific code, overridable by subadapters.
# File lib/sequel/adapters/jdbc.rb, line 672 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 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
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
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