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 165 def call_sproc(name, opts = OPTS) 166 args = opts[:args] || [] 167 sql = "{call #{name}(#{args.map{'?'}.join(',')})}" 168 synchronize(opts[:server]) do |conn| 169 cps = conn.prepareCall(sql) 170 171 i = 0 172 args.each{|arg| set_ps_arg(cps, arg, i+=1)} 173 174 begin 175 if block_given? 176 yield log_connection_yield(sql, conn){cps.executeQuery} 177 else 178 log_connection_yield(sql, conn){cps.executeUpdate} 179 if opts[:type] == :insert 180 last_insert_id(conn, opts) 181 end 182 end 183 rescue *DATABASE_ERROR_CLASSES => e 184 raise_error(e) 185 ensure 186 cps.close 187 end 188 end 189 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 193 def connect(server) 194 opts = server_opts(server) 195 conn = if jndi? 196 get_connection_from_jndi 197 else 198 args = [uri(opts)] 199 args.concat([opts[:user], opts[:password]]) if opts[:user] && opts[:password] 200 begin 201 JavaSQL::DriverManager.setLoginTimeout(opts[:login_timeout]) if opts[:login_timeout] 202 raise StandardError, "skipping regular connection" if opts[:jdbc_properties] 203 JavaSQL::DriverManager.getConnection(*args) 204 rescue StandardError, *DATABASE_ERROR_CLASSES => e 205 raise e unless driver 206 # If the DriverManager can't get the connection - use the connect 207 # method of the driver. (This happens under Tomcat for instance) 208 props = java.util.Properties.new 209 if opts && opts[:user] && opts[:password] 210 props.setProperty("user", opts[:user]) 211 props.setProperty("password", opts[:password]) 212 end 213 opts[:jdbc_properties].each{|k,v| props.setProperty(k.to_s, v)} if opts[:jdbc_properties] 214 begin 215 c = driver.new.connect(args[0], props) 216 raise(Sequel::DatabaseError, 'driver.new.connect returned nil: probably bad JDBC connection string') unless c 217 c 218 rescue StandardError, *DATABASE_ERROR_CLASSES => e2 219 if e2.respond_to?(:message=) && e2.message != e.message 220 e2.message = "#{e2.message}\n#{e.class.name}: #{e.message}" 221 end 222 raise e2 223 end 224 end 225 end 226 setup_connection_with_opts(conn, opts) 227 end
Close given adapter connections, and delete any related prepared statements.
# File lib/sequel/adapters/jdbc.rb 230 def disconnect_connection(c) 231 @connection_prepared_statements_mutex.synchronize{@connection_prepared_statements.delete(c)} 232 c.close 233 end
# File lib/sequel/adapters/jdbc.rb 235 def execute(sql, opts=OPTS, &block) 236 return call_sproc(sql, opts, &block) if opts[:sproc] 237 return execute_prepared_statement(sql, opts, &block) if [Symbol, Dataset].any?{|c| sql.is_a?(c)} 238 synchronize(opts[:server]) do |conn| 239 statement(conn) do |stmt| 240 if block 241 if size = fetch_size 242 stmt.setFetchSize(size) 243 end 244 yield log_connection_yield(sql, conn){stmt.executeQuery(sql)} 245 else 246 case opts[:type] 247 when :ddl 248 log_connection_yield(sql, conn){stmt.execute(sql)} 249 when :insert 250 log_connection_yield(sql, conn){execute_statement_insert(stmt, sql)} 251 last_insert_id(conn, Hash[opts].merge!(:stmt=>stmt)) 252 else 253 log_connection_yield(sql, conn){stmt.executeUpdate(sql)} 254 end 255 end 256 end 257 end 258 end
# File lib/sequel/adapters/jdbc.rb 261 def execute_ddl(sql, opts=OPTS) 262 opts = Hash[opts] 263 opts[:type] = :ddl 264 execute(sql, opts) 265 end
# File lib/sequel/adapters/jdbc.rb 267 def execute_insert(sql, opts=OPTS) 268 opts = Hash[opts] 269 opts[:type] = :insert 270 execute(sql, opts) 271 end
Use the JDBC
metadata to get a list of foreign keys for the table.
# File lib/sequel/adapters/jdbc.rb 280 def foreign_key_list(table, opts=OPTS) 281 m = output_identifier_meth 282 schema, table = metadata_schema_and_table(table, opts) 283 foreign_keys = {} 284 metadata(:getImportedKeys, nil, schema, table) do |r| 285 if fk = foreign_keys[r[:fk_name]] 286 fk[:columns] << [r[:key_seq], m.call(r[:fkcolumn_name])] 287 fk[:key] << [r[:key_seq], m.call(r[:pkcolumn_name])] 288 elsif r[:fk_name] 289 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])]]} 290 end 291 end 292 foreign_keys.values.each do |fk| 293 [:columns, :key].each do |k| 294 fk[k] = fk[k].sort.map{|_, v| v} 295 end 296 end 297 end
Sequel::Database#freeze
# File lib/sequel/adapters/jdbc.rb 273 def freeze 274 @type_convertor_map.freeze 275 @basic_type_convertor_map.freeze 276 super 277 end
Use the JDBC
metadata to get the index information for the table.
# File lib/sequel/adapters/jdbc.rb 300 def indexes(table, opts=OPTS) 301 m = output_identifier_meth 302 schema, table = metadata_schema_and_table(table, opts) 303 indexes = {} 304 metadata(:getIndexInfo, nil, schema, table, false, true) do |r| 305 next unless name = r[:column_name] 306 next if respond_to?(:primary_key_index_re, true) and r[:index_name] =~ primary_key_index_re 307 i = indexes[m.call(r[:index_name])] ||= {:columns=>[], :unique=>[false, 0].include?(r[:non_unique])} 308 i[:columns] << m.call(name) 309 end 310 indexes 311 end
Whether or not JNDI is being used for this connection.
# File lib/sequel/adapters/jdbc.rb 314 def jndi? 315 !!(uri =~ JNDI_URI_REGEXP) 316 end
All tables in this database
# File lib/sequel/adapters/jdbc.rb 319 def tables(opts=OPTS) 320 get_tables('TABLE', opts) 321 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 327 def uri(opts=OPTS) 328 opts = @opts.merge(opts) 329 ur = opts[:uri] || opts[:url] || opts[:database] 330 ur =~ /^\Ajdbc:/ ? ur : "jdbc:#{ur}" 331 end
All views in this database
# File lib/sequel/adapters/jdbc.rb 334 def views(opts=OPTS) 335 get_tables('VIEW', opts) 336 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 344 def adapter_initialize 345 @connection_prepared_statements = {} 346 @connection_prepared_statements_mutex = Mutex.new 347 @fetch_size = @opts[:fetch_size] ? typecast_value_integer(@opts[:fetch_size]) : default_fetch_size 348 @convert_types = typecast_value_boolean(@opts.fetch(:convert_types, true)) 349 raise(Error, "No connection string specified") unless uri 350 351 resolved_uri = jndi? ? get_uri_from_jndi : uri 352 setup_type_convertor_map_early 353 354 @driver = if (match = /\Ajdbc:([^:]+)/.match(resolved_uri)) && (prok = Sequel::Database.load_adapter(match[1].to_sym, :map=>DATABASE_SETUP, :subdir=>'jdbc')) 355 prok.call(self) 356 else 357 @opts[:driver] 358 end 359 360 setup_type_convertor_map 361 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 365 def cps_sync(conn, &block) 366 @connection_prepared_statements_mutex.synchronize{yield(@connection_prepared_statements[conn] ||= {})} 367 end
# File lib/sequel/adapters/jdbc.rb 369 def database_error_classes 370 DATABASE_ERROR_CLASSES 371 end
# File lib/sequel/adapters/jdbc.rb 373 def database_exception_sqlstate(exception, opts) 374 if database_exception_use_sqlstates? 375 while exception.respond_to?(:cause) 376 exception = exception.cause 377 return exception.getSQLState if exception.respond_to?(:getSQLState) 378 end 379 end 380 nil 381 end
# File lib/sequel/adapters/jdbc.rb 388 def dataset_class_default 389 Dataset 390 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 471 def default_fetch_size 472 nil 473 end
Raise a disconnect error if the SQL
state of the cause of the exception indicates so.
Sequel::Database#disconnect_error?
# File lib/sequel/adapters/jdbc.rb 393 def disconnect_error?(exception, opts) 394 cause = exception.respond_to?(:cause) ? exception.cause : exception 395 super || (cause.respond_to?(:getSQLState) && cause.getSQLState =~ /^08/) 396 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 405 def execute_prepared_statement(name, opts=OPTS) 406 args = opts[:arguments] 407 if name.is_a?(Dataset) 408 ps = name 409 name = ps.prepared_statement_name 410 else 411 ps = prepared_statement(name) 412 end 413 sql = ps.prepared_sql 414 synchronize(opts[:server]) do |conn| 415 if name and cps = cps_sync(conn){|cpsh| cpsh[name]} and cps[0] == sql 416 cps = cps[1] 417 else 418 log_connection_yield("CLOSE #{name}", conn){cps[1].close} if cps 419 if name 420 opts = Hash[opts] 421 opts[:name] = name 422 end 423 cps = log_connection_yield("PREPARE#{" #{name}:" if name} #{sql}", conn){prepare_jdbc_statement(conn, sql, opts)} 424 if size = fetch_size 425 cps.setFetchSize(size) 426 end 427 cps_sync(conn){|cpsh| cpsh[name] = [sql, cps]} if name 428 end 429 i = 0 430 args.each{|arg| set_ps_arg(cps, arg, i+=1)} 431 msg = "EXECUTE#{" #{name}" if name}" 432 if ps.log_sql 433 msg += " (" 434 msg << sql 435 msg << ")" 436 end 437 begin 438 if block_given? 439 yield log_connection_yield(msg, conn, args){cps.executeQuery} 440 else 441 case opts[:type] 442 when :ddl 443 log_connection_yield(msg, conn, args){cps.execute} 444 when :insert 445 log_connection_yield(msg, conn, args){execute_prepared_statement_insert(cps)} 446 last_insert_id(conn, Hash[opts].merge!(:prepared=>true, :stmt=>cps)) 447 else 448 log_connection_yield(msg, conn, args){cps.executeUpdate} 449 end 450 end 451 rescue *DATABASE_ERROR_CLASSES => e 452 raise_error(e) 453 ensure 454 cps.close unless name 455 end 456 end 457 end
Execute the prepared insert statement
# File lib/sequel/adapters/jdbc.rb 460 def execute_prepared_statement_insert(stmt) 461 stmt.executeUpdate 462 end
Execute the insert SQL
using the statement
# File lib/sequel/adapters/jdbc.rb 465 def execute_statement_insert(stmt, sql) 466 stmt.executeUpdate(sql) 467 end
Gets the connection from JNDI.
# File lib/sequel/adapters/jdbc.rb 476 def get_connection_from_jndi 477 jndi_name = JNDI_URI_REGEXP.match(uri)[1] 478 javax.naming.InitialContext.new.lookup(jndi_name).connection 479 end
Backbone of the tables and views support.
# File lib/sequel/adapters/jdbc.rb 490 def get_tables(type, opts) 491 ts = [] 492 m = output_identifier_meth 493 if schema = opts[:schema] 494 schema = schema.to_s 495 end 496 metadata(:getTables, nil, schema, nil, [type].to_java(:string)){|h| ts << m.call(h[:table_name])} 497 ts 498 end
Gets the JDBC
connection uri from the JNDI resource.
# File lib/sequel/adapters/jdbc.rb 482 def get_uri_from_jndi 483 conn = get_connection_from_jndi 484 conn.meta_data.url 485 ensure 486 conn.close if conn 487 end
Support Date objects used in bound variables
# File lib/sequel/adapters/jdbc.rb 501 def java_sql_date(date) 502 java.sql.Date.new(Time.local(date.year, date.month, date.day).to_i * 1000) 503 end
Support DateTime objects used in bound variables
# File lib/sequel/adapters/jdbc.rb 506 def java_sql_datetime(datetime) 507 ts = java.sql.Timestamp.new(Time.local(datetime.year, datetime.month, datetime.day, datetime.hour, datetime.min, datetime.sec).to_i * 1000) 508 ts.setNanos((datetime.sec_fraction * 1000000000).to_i) 509 ts 510 end
Support fractional seconds for Time objects used in bound variables
# File lib/sequel/adapters/jdbc.rb 513 def java_sql_timestamp(time) 514 ts = java.sql.Timestamp.new(time.to_i * 1000) 515 ts.setNanos(time.nsec) 516 ts 517 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 526 def last_insert_id(conn, opts) 527 nil 528 end
# File lib/sequel/adapters/jdbc.rb 519 def log_connection_execute(conn, sql) 520 statement(conn){|s| log_connection_yield(sql, conn){s.execute(sql)}} 521 end
Yield the metadata for this database
# File lib/sequel/adapters/jdbc.rb 531 def metadata(*args, &block) 532 synchronize do |c| 533 result = c.getMetaData.public_send(*args) 534 begin 535 metadata_dataset.send(:process_result_set, result, &block) 536 ensure 537 result.close 538 end 539 end 540 end
Return the schema and table suitable for use with metadata queries.
# File lib/sequel/adapters/jdbc.rb 543 def metadata_schema_and_table(table, opts) 544 im = input_identifier_meth(opts[:dataset]) 545 schema, table = schema_and_table(table) 546 schema ||= opts[:schema] 547 schema = im.call(schema) if schema 548 table = im.call(table) 549 [schema, table] 550 end
# File lib/sequel/adapters/jdbc.rb 605 def schema_column_set_db_type(schema) 606 case schema[:type] 607 when :string 608 if schema[:db_type] =~ /\A(character( varying)?|n?(var)?char2?)\z/io && schema[:column_size] > 0 609 schema[:db_type] += "(#{schema[:column_size]})" 610 end 611 when :decimal 612 if schema[:db_type] =~ /\A(decimal|numeric)\z/io && schema[:column_size] > 0 && schema[:scale] >= 0 613 schema[:db_type] += "(#{schema[:column_size]}, #{schema[:scale]})" 614 end 615 end 616 end
# File lib/sequel/adapters/jdbc.rb 618 def schema_parse_table(table, opts=OPTS) 619 m = output_identifier_meth(opts[:dataset]) 620 schema, table = metadata_schema_and_table(table, opts) 621 pks, ts = [], [] 622 metadata(:getPrimaryKeys, nil, schema, table) do |h| 623 next if schema_parse_table_skip?(h, schema) 624 pks << h[:column_name] 625 end 626 schemas = [] 627 metadata(:getColumns, nil, schema, table, nil) do |h| 628 next if schema_parse_table_skip?(h, schema) 629 s = { 630 :type=>schema_column_type(h[:type_name]), 631 :db_type=>h[:type_name], 632 :default=>(h[:column_def] == '' ? nil : h[:column_def]), 633 :allow_null=>(h[:nullable] != 0), 634 :primary_key=>pks.include?(h[:column_name]), 635 :column_size=>h[:column_size], 636 :scale=>h[:decimal_digits], 637 :remarks=>h[:remarks] 638 } 639 if s[:primary_key] 640 s[:auto_increment] = h[:is_autoincrement] == "YES" 641 end 642 s[:max_length] = s[:column_size] if s[:type] == :string 643 if s[:db_type] =~ /number|numeric|decimal/i && s[:scale] == 0 644 s[:type] = :integer 645 end 646 schema_column_set_db_type(s) 647 schemas << h[:table_schem] unless schemas.include?(h[:table_schem]) 648 ts << [m.call(h[:column_name]), s] 649 end 650 if schemas.length > 1 651 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.' 652 end 653 ts 654 end
Skip tables in the INFORMATION_SCHEMA when parsing columns.
# File lib/sequel/adapters/jdbc.rb 657 def schema_parse_table_skip?(h, schema) 658 h[:table_schem] == 'INFORMATION_SCHEMA' 659 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 561 def set_ps_arg(cps, arg, i) 562 case arg 563 when Integer 564 cps.setLong(i, arg) 565 when Sequel::SQL::Blob 566 cps.setBytes(i, arg.to_java_bytes) 567 when String 568 cps.setString(i, arg) 569 when Float 570 cps.setDouble(i, arg) 571 when TrueClass, FalseClass 572 cps.setBoolean(i, arg) 573 when NilClass 574 set_ps_arg_nil(cps, i) 575 when DateTime 576 cps.setTimestamp(i, java_sql_datetime(arg)) 577 when Date 578 cps.setDate(i, java_sql_date(arg)) 579 when Time 580 cps.setTimestamp(i, java_sql_timestamp(arg)) 581 when Java::JavaSql::Timestamp 582 cps.setTimestamp(i, arg) 583 when Java::JavaSql::Date 584 cps.setDate(i, arg) 585 else 586 cps.setObject(i, arg) 587 end 588 end
Use setString with a nil value by default, but this doesn't work on all subadapters.
# File lib/sequel/adapters/jdbc.rb 591 def set_ps_arg_nil(cps, i) 592 cps.setString(i, nil) 593 end
Return the connection. Can be overridden in subadapters for database specific setup.
# File lib/sequel/adapters/jdbc.rb 596 def setup_connection(conn) 597 conn 598 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 601 def setup_connection_with_opts(conn, opts) 602 setup_connection(conn) 603 end
Called after loading subadapter-specific code, overridable by subadapters.
# File lib/sequel/adapters/jdbc.rb 662 def setup_type_convertor_map 663 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 667 def setup_type_convertor_map_early 668 @type_convertor_map = TypeConvertor::MAP.merge(Java::JavaSQL::Types::TIMESTAMP=>method(:timestamp_convert)) 669 @basic_type_convertor_map = TypeConvertor::BASIC_MAP.dup 670 end
Yield a new statement object, and ensure that it is closed before returning.
# File lib/sequel/adapters/jdbc.rb 673 def statement(conn) 674 stmt = conn.createStatement 675 yield stmt 676 rescue *DATABASE_ERROR_CLASSES => e 677 raise_error(e) 678 ensure 679 stmt.close if stmt 680 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 684 def timestamp_convert(r, i) 685 if v = r.getTimestamp(i) 686 to_application_timestamp([v.getYear + 1900, v.getMonth + 1, v.getDate, v.getHours, v.getMinutes, v.getSeconds, v.getNanos]) 687 end 688 end