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