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 164 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 192 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 229 def disconnect_connection(c) @connection_prepared_statements_mutex.synchronize{@connection_prepared_statements.delete(c)} c.close end
# File lib/sequel/adapters/jdbc.rb, line 234 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
# File lib/sequel/adapters/jdbc.rb, line 260 def execute_ddl(sql, opts=OPTS) opts = Hash[opts] opts[:type] = :ddl execute(sql, opts) end
# File lib/sequel/adapters/jdbc.rb, line 266 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 279 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 272 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 299 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 313 def jndi? !!(uri =~ JNDI_URI_REGEXP) end
All tables in this database
# File lib/sequel/adapters/jdbc.rb, line 318 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 326 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 333 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 343 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 364 def cps_sync(conn, &block) @connection_prepared_statements_mutex.synchronize{yield(@connection_prepared_statements[conn] ||= {})} end
# File lib/sequel/adapters/jdbc.rb, line 368 def database_error_classes DATABASE_ERROR_CLASSES end
# File lib/sequel/adapters/jdbc.rb, line 372 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 387 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 470 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 392 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 404 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)} last_insert_id(conn, Hash[opts].merge!(:prepared=>true, :stmt=>cps)) 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 459 def execute_prepared_statement_insert(stmt) stmt.executeUpdate end
Execute the insert SQL using the statement
# File lib/sequel/adapters/jdbc.rb, line 464 def execute_statement_insert(stmt, sql) stmt.executeUpdate(sql) end
Gets the connection from JNDI.
# File lib/sequel/adapters/jdbc.rb, line 475 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 489 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 481 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 500 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 505 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 512 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 525 def last_insert_id(conn, opts) nil end
# File lib/sequel/adapters/jdbc.rb, line 518 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 530 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 542 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 604 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 617 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 656 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 560 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 590 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 595 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 600 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 661 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 666 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 672 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 683 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