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 189 def call_sproc(name, opts = OPTS) 190 args = opts[:args] || [] 191 sql = "{call #{name}(#{args.map{'?'}.join(',')})}" 192 synchronize(opts[:server]) do |conn| 193 cps = conn.prepareCall(sql) 194 195 i = 0 196 args.each{|arg| set_ps_arg(cps, arg, i+=1)} 197 198 begin 199 if defined?(yield) 200 yield log_connection_yield(sql, conn){cps.executeQuery} 201 else 202 log_connection_yield(sql, conn){cps.executeUpdate} 203 if opts[:type] == :insert 204 last_insert_id(conn, opts) 205 end 206 end 207 rescue *DATABASE_ERROR_CLASSES => e 208 raise_error(e) 209 ensure 210 cps.close 211 end 212 end 213 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 217 def connect(server) 218 opts = server_opts(server) 219 conn = if jndi? 220 get_connection_from_jndi 221 else 222 args = [uri(opts)] 223 args.concat([opts[:user], opts[:password]]) if opts[:user] && opts[:password] 224 begin 225 JavaSQL::DriverManager.setLoginTimeout(opts[:login_timeout]) if opts[:login_timeout] 226 raise StandardError, "skipping regular connection" if opts[:jdbc_properties] 227 JavaSQL::DriverManager.getConnection(*args) 228 rescue StandardError, *DATABASE_ERROR_CLASSES => e 229 raise e unless driver 230 # If the DriverManager can't get the connection - use the connect 231 # method of the driver. (This happens under Tomcat for instance) 232 props = java.util.Properties.new 233 if opts && opts[:user] && opts[:password] 234 props.setProperty("user", opts[:user]) 235 props.setProperty("password", opts[:password]) 236 end 237 opts[:jdbc_properties].each{|k,v| props.setProperty(k.to_s, v)} if opts[:jdbc_properties] 238 begin 239 c = driver.new.connect(args[0], props) 240 raise(Sequel::DatabaseError, 'driver.new.connect returned nil: probably bad JDBC connection string') unless c 241 c 242 rescue StandardError, *DATABASE_ERROR_CLASSES => e2 243 if e2.respond_to?(:message=) && e2.message != e.message 244 e2.message = "#{e2.message}\n#{e.class.name}: #{e.message}" 245 end 246 raise e2 247 end 248 end 249 end 250 setup_connection_with_opts(conn, opts) 251 end
Close given adapter connections, and delete any related prepared statements.
# File lib/sequel/adapters/jdbc.rb 254 def disconnect_connection(c) 255 @connection_prepared_statements_mutex.synchronize{@connection_prepared_statements.delete(c)} 256 c.close 257 end
# File lib/sequel/adapters/jdbc.rb 259 def execute(sql, opts=OPTS, &block) 260 return call_sproc(sql, opts, &block) if opts[:sproc] 261 return execute_prepared_statement(sql, opts, &block) if [Symbol, Dataset].any?{|c| sql.is_a?(c)} 262 synchronize(opts[:server]) do |conn| 263 statement(conn) do |stmt| 264 if block 265 if size = fetch_size 266 stmt.setFetchSize(size) 267 end 268 yield log_connection_yield(sql, conn){stmt.executeQuery(sql)} 269 else 270 case opts[:type] 271 when :ddl 272 log_connection_yield(sql, conn){stmt.execute(sql)} 273 when :insert 274 log_connection_yield(sql, conn){execute_statement_insert(stmt, sql)} 275 opts = Hash[opts] 276 opts[:stmt] = stmt 277 last_insert_id(conn, opts) 278 else 279 log_connection_yield(sql, conn){stmt.executeUpdate(sql)} 280 end 281 end 282 end 283 end 284 end
# File lib/sequel/adapters/jdbc.rb 287 def execute_ddl(sql, opts=OPTS) 288 opts = Hash[opts] 289 opts[:type] = :ddl 290 execute(sql, opts) 291 end
# File lib/sequel/adapters/jdbc.rb 293 def execute_insert(sql, opts=OPTS) 294 opts = Hash[opts] 295 opts[:type] = :insert 296 execute(sql, opts) 297 end
Use the JDBC
metadata to get a list of foreign keys for the table.
# File lib/sequel/adapters/jdbc.rb 306 def foreign_key_list(table, opts=OPTS) 307 m = output_identifier_meth 308 schema, table = metadata_schema_and_table(table, opts) 309 foreign_keys = {} 310 metadata(:getImportedKeys, nil, schema, table) do |r| 311 if fk = foreign_keys[r[:fk_name]] 312 fk[:columns] << [r[:key_seq], m.call(r[:fkcolumn_name])] 313 fk[:key] << [r[:key_seq], m.call(r[:pkcolumn_name])] 314 elsif r[:fk_name] 315 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])]]} 316 end 317 end 318 foreign_keys.values.each do |fk| 319 [:columns, :key].each do |k| 320 fk[k] = fk[k].sort.map{|_, v| v} 321 end 322 end 323 end
Sequel::Database#freeze
# File lib/sequel/adapters/jdbc.rb 299 def freeze 300 @type_convertor_map.freeze 301 @basic_type_convertor_map.freeze 302 super 303 end
Use the JDBC
metadata to get the index information for the table.
# File lib/sequel/adapters/jdbc.rb 326 def indexes(table, opts=OPTS) 327 m = output_identifier_meth 328 schema, table = metadata_schema_and_table(table, opts) 329 indexes = {} 330 metadata(:getIndexInfo, nil, schema, table, false, true) do |r| 331 next unless name = r[:column_name] 332 next if respond_to?(:primary_key_index_re, true) and r[:index_name] =~ primary_key_index_re 333 i = indexes[m.call(r[:index_name])] ||= {:columns=>[], :unique=>[false, 0].include?(r[:non_unique])} 334 i[:columns] << m.call(name) 335 end 336 indexes 337 end
Whether or not JNDI is being used for this connection.
# File lib/sequel/adapters/jdbc.rb 340 def jndi? 341 !!(uri =~ JNDI_URI_REGEXP) 342 end
All tables in this database
# File lib/sequel/adapters/jdbc.rb 345 def tables(opts=OPTS) 346 get_tables('TABLE', opts) 347 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 353 def uri(opts=OPTS) 354 opts = @opts.merge(opts) 355 ur = opts[:uri] || opts[:url] || opts[:database] 356 ur =~ /^\Ajdbc:/ ? ur : "jdbc:#{ur}" 357 end
All views in this database
# File lib/sequel/adapters/jdbc.rb 360 def views(opts=OPTS) 361 get_tables('VIEW', opts) 362 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 370 def adapter_initialize 371 @connection_prepared_statements = {} 372 @connection_prepared_statements_mutex = Mutex.new 373 @fetch_size = @opts[:fetch_size] ? typecast_value_integer(@opts[:fetch_size]) : default_fetch_size 374 @convert_types = typecast_value_boolean(@opts.fetch(:convert_types, true)) 375 raise(Error, "No connection string specified") unless uri 376 377 resolved_uri = jndi? ? get_uri_from_jndi : uri 378 setup_type_convertor_map_early 379 380 @driver = if (match = /\Ajdbc:([^:]+)/.match(resolved_uri)) && (prok = Sequel::Database.load_adapter(match[1].to_sym, :map=>DATABASE_SETUP, :subdir=>'jdbc')) 381 prok.call(self) 382 else 383 @opts[:driver] 384 end 385 386 setup_type_convertor_map 387 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 391 def cps_sync(conn, &block) 392 @connection_prepared_statements_mutex.synchronize{yield(@connection_prepared_statements[conn] ||= {})} 393 end
# File lib/sequel/adapters/jdbc.rb 395 def database_error_classes 396 DATABASE_ERROR_CLASSES 397 end
# File lib/sequel/adapters/jdbc.rb 399 def database_exception_sqlstate(exception, opts) 400 if database_exception_use_sqlstates? 401 while exception.respond_to?(:cause) 402 exception = exception.cause 403 return exception.getSQLState if exception.respond_to?(:getSQLState) 404 end 405 end 406 nil 407 end
# File lib/sequel/adapters/jdbc.rb 414 def dataset_class_default 415 Dataset 416 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 500 def default_fetch_size 501 nil 502 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 419 def disconnect_error?(exception, opts) 420 cause = exception.respond_to?(:cause) ? exception.cause : exception 421 super || (cause.respond_to?(:getSQLState) && cause.getSQLState =~ /^08/) 422 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 431 def execute_prepared_statement(name, opts=OPTS) 432 args = opts[:arguments] 433 if name.is_a?(Dataset) 434 ps = name 435 name = ps.prepared_statement_name 436 else 437 ps = prepared_statement(name) 438 end 439 sql = ps.prepared_sql 440 synchronize(opts[:server]) do |conn| 441 if name and cps = cps_sync(conn){|cpsh| cpsh[name]} and cps[0] == sql 442 cps = cps[1] 443 else 444 log_connection_yield("CLOSE #{name}", conn){cps[1].close} if cps 445 if name 446 opts = Hash[opts] 447 opts[:name] = name 448 end 449 cps = log_connection_yield("PREPARE#{" #{name}:" if name} #{sql}", conn){prepare_jdbc_statement(conn, sql, opts)} 450 if size = fetch_size 451 cps.setFetchSize(size) 452 end 453 cps_sync(conn){|cpsh| cpsh[name] = [sql, cps]} if name 454 end 455 i = 0 456 args.each{|arg| set_ps_arg(cps, arg, i+=1)} 457 msg = "EXECUTE#{" #{name}" if name}" 458 if ps.log_sql 459 msg += " (" 460 msg << sql 461 msg << ")" 462 end 463 begin 464 if defined?(yield) 465 yield log_connection_yield(msg, conn, args){cps.executeQuery} 466 else 467 case opts[:type] 468 when :ddl 469 log_connection_yield(msg, conn, args){cps.execute} 470 when :insert 471 log_connection_yield(msg, conn, args){execute_prepared_statement_insert(cps)} 472 opts = Hash[opts] 473 opts[:prepared] = true 474 opts[:stmt] = cps 475 last_insert_id(conn, opts) 476 else 477 log_connection_yield(msg, conn, args){cps.executeUpdate} 478 end 479 end 480 rescue *DATABASE_ERROR_CLASSES => e 481 raise_error(e) 482 ensure 483 cps.close unless name 484 end 485 end 486 end
Execute the prepared insert statement
# File lib/sequel/adapters/jdbc.rb 489 def execute_prepared_statement_insert(stmt) 490 stmt.executeUpdate 491 end
Execute the insert SQL
using the statement
# File lib/sequel/adapters/jdbc.rb 494 def execute_statement_insert(stmt, sql) 495 stmt.executeUpdate(sql) 496 end
Gets the connection from JNDI.
# File lib/sequel/adapters/jdbc.rb 505 def get_connection_from_jndi 506 jndi_name = JNDI_URI_REGEXP.match(uri)[1] 507 javax.naming.InitialContext.new.lookup(jndi_name).connection 508 end
Backbone of the tables and views support.
# File lib/sequel/adapters/jdbc.rb 519 def get_tables(type, opts) 520 ts = [] 521 m = output_identifier_meth 522 if schema = opts[:schema] 523 schema = schema.to_s 524 end 525 metadata(:getTables, nil, schema, nil, [type].to_java(:string)){|h| ts << m.call(h[:table_name])} 526 ts 527 end
Gets the JDBC
connection uri from the JNDI resource.
# File lib/sequel/adapters/jdbc.rb 511 def get_uri_from_jndi 512 conn = get_connection_from_jndi 513 conn.meta_data.url 514 ensure 515 conn.close if conn 516 end
Support Date objects used in bound variables
# File lib/sequel/adapters/jdbc.rb 530 def java_sql_date(date) 531 java.sql.Date.new(Time.local(date.year, date.month, date.day).to_i * 1000) 532 end
Support DateTime objects used in bound variables
# File lib/sequel/adapters/jdbc.rb 535 def java_sql_datetime(datetime) 536 ts = java.sql.Timestamp.new(Time.local(datetime.year, datetime.month, datetime.day, datetime.hour, datetime.min, datetime.sec).to_i * 1000) 537 ts.setNanos((datetime.sec_fraction * 1000000000).to_i) 538 ts 539 end
Support fractional seconds for Time objects used in bound variables
# File lib/sequel/adapters/jdbc.rb 542 def java_sql_timestamp(time) 543 ts = java.sql.Timestamp.new(time.to_i * 1000) 544 ts.setNanos(time.nsec) 545 ts 546 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 555 def last_insert_id(conn, opts) 556 nil 557 end
# File lib/sequel/adapters/jdbc.rb 548 def log_connection_execute(conn, sql) 549 statement(conn){|s| log_connection_yield(sql, conn){s.execute(sql)}} 550 end
Yield the metadata for this database
# File lib/sequel/adapters/jdbc.rb 560 def metadata(*args, &block) 561 synchronize do |c| 562 result = c.getMetaData.public_send(*args) 563 begin 564 metadata_dataset.send(:process_result_set, result, &block) 565 ensure 566 result.close 567 end 568 end 569 end
Return the schema and table suitable for use with metadata queries.
# File lib/sequel/adapters/jdbc.rb 572 def metadata_schema_and_table(table, opts) 573 im = input_identifier_meth(opts[:dataset]) 574 schema, table = schema_and_table(table) 575 schema ||= opts[:schema] 576 schema = im.call(schema) if schema 577 table = im.call(table) 578 [schema, table] 579 end
# File lib/sequel/adapters/jdbc.rb 634 def schema_column_set_db_type(schema) 635 case schema[:type] 636 when :string 637 if schema[:db_type] =~ /\A(character( varying)?|n?(var)?char2?)\z/io && schema[:column_size] > 0 638 schema[:db_type] += "(#{schema[:column_size]})" 639 end 640 when :decimal 641 if schema[:db_type] =~ /\A(decimal|numeric)\z/io && schema[:column_size] > 0 && schema[:scale] >= 0 642 schema[:db_type] += "(#{schema[:column_size]}, #{schema[:scale]})" 643 end 644 end 645 end
# File lib/sequel/adapters/jdbc.rb 647 def schema_parse_table(table, opts=OPTS) 648 m = output_identifier_meth(opts[:dataset]) 649 schema, table = metadata_schema_and_table(table, opts) 650 pks, ts = [], [] 651 metadata(:getPrimaryKeys, nil, schema, table) do |h| 652 next if schema_parse_table_skip?(h, schema) 653 pks << h[:column_name] 654 end 655 schemas = [] 656 metadata(:getColumns, nil, schema, table, nil) do |h| 657 next if schema_parse_table_skip?(h, schema) 658 s = { 659 :type=>schema_column_type(h[:type_name]), 660 :db_type=>h[:type_name], 661 :default=>(h[:column_def] == '' ? nil : h[:column_def]), 662 :allow_null=>(h[:nullable] != 0), 663 :primary_key=>pks.include?(h[:column_name]), 664 :column_size=>h[:column_size], 665 :scale=>h[:decimal_digits], 666 :remarks=>h[:remarks] 667 } 668 if s[:primary_key] 669 s[:auto_increment] = h[:is_autoincrement] == "YES" 670 end 671 s[:max_length] = s[:column_size] if s[:type] == :string 672 if s[:db_type] =~ /number|numeric|decimal/i && s[:scale] == 0 673 s[:type] = :integer 674 end 675 schema_column_set_db_type(s) 676 schemas << h[:table_schem] unless schemas.include?(h[:table_schem]) 677 ts << [m.call(h[:column_name]), s] 678 end 679 if schemas.length > 1 680 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.' 681 end 682 ts 683 end
Skip tables in the INFORMATION_SCHEMA when parsing columns.
# File lib/sequel/adapters/jdbc.rb 686 def schema_parse_table_skip?(h, schema) 687 h[:table_schem] == 'INFORMATION_SCHEMA' 688 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 590 def set_ps_arg(cps, arg, i) 591 case arg 592 when Integer 593 cps.setLong(i, arg) 594 when Sequel::SQL::Blob 595 cps.setBytes(i, arg.to_java_bytes) 596 when String 597 cps.setString(i, arg) 598 when Float 599 cps.setDouble(i, arg) 600 when TrueClass, FalseClass 601 cps.setBoolean(i, arg) 602 when NilClass 603 set_ps_arg_nil(cps, i) 604 when DateTime 605 cps.setTimestamp(i, java_sql_datetime(arg)) 606 when Date 607 cps.setDate(i, java_sql_date(arg)) 608 when Time 609 cps.setTimestamp(i, java_sql_timestamp(arg)) 610 when Java::JavaSql::Timestamp 611 cps.setTimestamp(i, arg) 612 when Java::JavaSql::Date 613 cps.setDate(i, arg) 614 else 615 cps.setObject(i, arg) 616 end 617 end
Use setString with a nil value by default, but this doesn't work on all subadapters.
# File lib/sequel/adapters/jdbc.rb 620 def set_ps_arg_nil(cps, i) 621 cps.setString(i, nil) 622 end
Return the connection. Can be overridden in subadapters for database specific setup.
# File lib/sequel/adapters/jdbc.rb 625 def setup_connection(conn) 626 conn 627 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 630 def setup_connection_with_opts(conn, opts) 631 setup_connection(conn) 632 end
Called after loading subadapter-specific code, overridable by subadapters.
# File lib/sequel/adapters/jdbc.rb 691 def setup_type_convertor_map 692 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 696 def setup_type_convertor_map_early 697 @type_convertor_map = TypeConvertor::MAP.merge(Java::JavaSQL::Types::TIMESTAMP=>method(:timestamp_convert)) 698 @basic_type_convertor_map = TypeConvertor::BASIC_MAP.dup 699 end
Yield a new statement object, and ensure that it is closed before returning.
# File lib/sequel/adapters/jdbc.rb 702 def statement(conn) 703 stmt = conn.createStatement 704 yield stmt 705 rescue *DATABASE_ERROR_CLASSES => e 706 raise_error(e) 707 ensure 708 stmt.close if stmt 709 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 713 def timestamp_convert(r, i) 714 if v = r.getTimestamp(i) 715 to_application_timestamp([v.getYear + 1900, v.getMonth + 1, v.getDate, v.getHours, v.getMinutes, v.getSeconds, v.getNanos]) 716 end 717 end