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