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