Changeset 3226
- Timestamp:
- 12/07/05 06:26:54 (3 years ago)
- Files:
-
- branches/stable/activerecord/CHANGELOG (modified) (2 diffs)
- branches/stable/activerecord/lib/active_record/base.rb (modified) (1 diff)
- branches/stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb (modified) (2 diffs)
- branches/stable/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb (modified) (3 diffs)
- branches/stable/activerecord/lib/active_record/connection_adapters/firebird_adapter.rb (modified) (5 diffs)
- branches/stable/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb (modified) (7 diffs)
- branches/stable/activerecord/lib/active_record/connection_adapters/oci_adapter.rb (modified) (6 diffs)
- branches/stable/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb (modified) (4 diffs)
- branches/stable/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb (modified) (3 diffs)
- branches/stable/activerecord/lib/active_record/fixtures.rb (modified) (1 diff)
- branches/stable/railties/lib/dispatcher.rb (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
branches/stable/activerecord/CHANGELOG
r3204 r3226 1 1 * SVN* 2 2 3 * MySQL: work around ruby-mysql/mysql-ruby inconsistency with mysql.stat. Eliminate usage of mysql.ping because it doesn't guarantee reconnect. Explicitly close and reopen the connection instead. [Jeremy Kemper] 4 5 * When AbstractAdapter#log rescues an exception, attempt to detect and reconnect to an inactive database connection. Connection adapter must respond to the active? and reconnect! instance methods. Initial support for PostgreSQL, MySQL, and SQLite. Make certain that all statements which may need reconnection are performed within a logged block: for example, this means no avoiding log(sql, name) { } if @logger.nil? [Jeremy Kemper] 6 7 * Firebird: active? and reconnect! methods for handling stale connections. #428 [Ken Kunz <kennethkunz@gmail.com>] 8 3 9 * Firebird: updated for FireRuby 0.4.0. #3009 [Ken Kunz <kennethkunz@gmail.com>] 4 10 5 11 * Introducing the Firebird adapter. Quote columns and use attribute_condition more consistently. Setup guide: http://wiki.rubyonrails.com/rails/pages/Firebird+Adapter #1874 [Ken Kunz <kennethkunz@gmail.com>] 6 12 13 * MySQL and PostgreSQL: active? compatibility with the pure-Ruby driver. #428 [Jeremy Kemper] 14 15 * Oracle: active? check pings the database rather than testing the last command status. #428 [Michael Schoen] 16 7 17 * SQLServer: resolve column aliasing/quoting collision when using limit or offset in an eager find. #2974 [kajism@yahoo.com] 8 18 … … 10 20 11 21 * Fixed bug where using update_attribute after pushing a record to a habtm association of the object caused duplicate rows in the join table. #2888 [colman@rominato.com, Florian Weber, Michael Schoen] 22 23 * MySQL: introduce :encoding option to specify the character set for client, connection, and results. Only available for MySQL 4.1 and later with the mysql-ruby driver. Do SHOW CHARACTER SET in mysql client to see available encodings. #2975 [Shugo Maeda] 12 24 13 25 * Add tasks to create, drop and rebuild the MySQL and PostgreSQL test databases. [Marcel Molina Jr.] branches/stable/activerecord/lib/active_record/base.rb
r3177 r3226 244 244 cattr_accessor :logger 245 245 246 # Returns the connection currently associated with the class. This can247 # also be used to "borrow" the connection to do database work unrelated248 # to any of the specific Active Records.249 def self.connection250 if allow_concurrency251 retrieve_connection252 else253 @connection ||= retrieve_connection254 end255 end256 257 # Returns the connection currently associated with the class. This can258 # also be used to "borrow" the connection to do database work that isn't259 # easily done without going straight to SQL.260 def connection261 self.class.connection262 end263 264 246 def self.inherited(child) #:nodoc: 265 247 @@subclasses[self] ||= [] branches/stable/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
r3177 r3226 33 33 'Abstract' 34 34 end 35 35 36 36 # Does this adapter support migrations? Backend specific, as the 37 37 # abstract adapter always returns +false+. … … 49 49 50 50 def reset_runtime #:nodoc: 51 rt = @runtime 52 @runtime = 0 53 return rt 51 rt, @runtime = @runtime, 0 52 rt 54 53 end 55 54 56 protected 55 56 # CONNECTION MANAGEMENT ==================================== 57 58 # Is this connection active and ready to perform queries? 59 def active? 60 true 61 end 62 63 # Close this connection and open a new one in its place. 64 def reconnect! 65 end 66 67 68 protected 57 69 def log(sql, name) 58 begin 59 if block_given? 60 if @logger and @logger.level <= Logger::INFO 61 result = nil 62 seconds = Benchmark.realtime { result = yield } 63 @runtime += seconds 64 log_info(sql, name, seconds) 65 result 66 else 67 yield 68 end 70 if block_given? 71 if @logger and @logger.level <= Logger::INFO 72 result = nil 73 seconds = Benchmark.realtime { result = yield } 74 @runtime += seconds 75 log_info(sql, name, seconds) 76 result 69 77 else 70 log_info(sql, name, 0) 71 nil 78 yield 72 79 end 73 rescue Exception =>e74 log_info( "#{e.message}: #{sql}", name, 0)75 raise ActiveRecord::StatementInvalid, "#{e.message}: #{sql}"80 else 81 log_info(sql, name, 0) 82 nil 76 83 end 84 rescue Exception => e 85 # Log message and raise exception. 86 message = "#{e.class.name}: #{e.message}: #{sql}" 87 log_info(message, name, 0) 88 raise ActiveRecord::StatementInvalid, message 77 89 end 78 90 branches/stable/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
r3225 r3226 10 10 # The class -> [adapter_method, config] map 11 11 @@defined_connections = {} 12 13 # The class -> thread id -> adapter cache. 14 @@connection_cache = Hash.new { |h, k| h[k] = Hash.new } 15 16 # Returns the connection currently associated with the class. This can 17 # also be used to "borrow" the connection to do database work unrelated 18 # to any of the specific Active Records. 19 def self.connection 20 @@connection_cache[Thread.current.object_id][name] ||= retrieve_connection 21 end 22 23 # Clears the cache which maps classes to connections. 24 def self.clear_connection_cache! 25 @@connection_cache.clear 26 end 27 28 # Returns the connection currently associated with the class. This can 29 # also be used to "borrow" the connection to do database work that isn't 30 # easily done without going straight to SQL. 31 def connection 32 self.class.connection 33 end 12 34 13 35 # Establishes the connection to the database. Accepts a hash as input where … … 78 100 until klass == ar_super 79 101 if conn = active_connections[klass.name] 102 # Reconnect if the connection is inactive. 103 conn.reconnect! unless conn.active? 80 104 return conn 81 105 elsif conn = @@defined_connections[klass.name] … … 108 132 conn = @@defined_connections[klass.name] 109 133 @@defined_connections.delete(klass.name) 110 active_connections[klass.name] = nil 134 @@connection_cache[Thread.current.object_id].delete(klass.name) 135 active_connections.delete(klass.name) 111 136 @connection = nil 112 137 conn.config if conn branches/stable/activerecord/lib/active_record/connection_adapters/firebird_adapter.rb
r3204 r3226 32 32 end 33 33 options = config[:charset] ? { CHARACTER_SET => config[:charset] } : {} 34 connection_params = [config[:username], config[:password], options] 34 35 db = FireRuby::Database.new_from_params(*config.values_at(:database, :host, :port, :service)) 35 connection = db.connect( config[:username], config[:password], options)36 ConnectionAdapters::FirebirdAdapter.new(connection, logger )36 connection = db.connect(*connection_params) 37 ConnectionAdapters::FirebirdAdapter.new(connection, logger, connection_params) 37 38 end 38 39 end … … 241 242 cattr_accessor :boolean_domain 242 243 244 def initialize(connection, logger, connection_params=nil) 245 super(connection, logger) 246 @connection_params = connection_params 247 end 248 243 249 def adapter_name # :nodoc: 244 250 'Firebird' … … 254 260 "#{table_name}_seq" 255 261 end 262 256 263 257 264 # QUOTING ================================================== … … 280 287 quote(boolean_domain[:false]) 281 288 end 289 290 291 # CONNECTION MANAGEMENT ==================================== 292 293 def active? 294 not @connection.closed? 295 end 296 297 def reconnect! 298 @connection.close 299 @connection = @connection.database.connect(*@connection_params) 300 end 301 282 302 283 303 # DATABASE STATEMENTS ====================================== … … 340 360 FireRuby::Generator.new(sequence_name, @connection).next(1) 341 361 end 362 342 363 343 364 # SCHEMA STATEMENTS ======================================== branches/stable/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
r3171 r3226 39 39 mysql = Mysql.init 40 40 mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslkey] 41 ConnectionAdapters::MysqlAdapter.new(mysql.real_connect(host, username, password, database, port, socket), logger, [host, username, password, database, port, socket]) 41 if config[:encoding] 42 begin 43 mysql.options(Mysql::SET_CHARSET_NAME, config[:encoding]) 44 rescue 45 raise ActiveRecord::ConnectionFailed, 'The :encoding option is only available for MySQL 4.1 and later with the mysql-ruby driver. Again, this does not work with the ruby-mysql driver or MySQL < 4.1.' 46 end 47 end 48 49 conn = mysql.real_connect(host, username, password, database, port, socket) 50 conn.query("SET NAMES '#{config[:encoding]}'") if config[:encoding] 51 ConnectionAdapters::MysqlAdapter.new(conn, logger, [host, username, password, database, port, socket], mysql) 42 52 end 43 53 end … … 88 98 ] 89 99 90 def initialize(connection, logger, connection_options=nil )100 def initialize(connection, logger, connection_options=nil, mysql=Mysql) 91 101 super(connection, logger) 92 102 @connection_options = connection_options 103 @mysql = mysql 93 104 end 94 105 … … 120 131 # QUOTING ================================================== 121 132 133 def quote(value, column = nil) 134 if value.kind_of?(String) && column && column.type == :binary 135 s = column.class.string_to_binary(value).unpack("H*")[0] 136 "x'#{s}'" 137 else 138 super 139 end 140 end 141 122 142 def quote_column_name(name) #:nodoc: 123 143 "`#{name}`" … … 125 145 126 146 def quote_string(string) #:nodoc: 127 Mysql::quote(string)147 @mysql.quote(string) 128 148 end 129 149 … … 137 157 138 158 159 # CONNECTION MANAGEMENT ==================================== 160 161 def active? 162 if @connection.respond_to?(:stat) 163 @connection.stat 164 else 165 @connection.query 'select 1' 166 end 167 168 # mysql-ruby doesn't raise an exception when stat fails. 169 if @connection.respond_to?(:errno) 170 @connection.errno.zero? 171 else 172 true 173 end 174 rescue Mysql::Error 175 false 176 end 177 178 def reconnect! 179 @connection.close rescue nil 180 connect 181 end 182 183 139 184 # DATABASE STATEMENTS ====================================== 140 185 … … 149 194 150 195 def execute(sql, name = nil, retries = 2) #:nodoc: 151 unless @logger 152 @connection.query(sql) 153 else 154 log(sql, name) { @connection.query(sql) } 155 end 196 log(sql, name) { @connection.query(sql) } 156 197 rescue ActiveRecord::StatementInvalid => exception 157 if LOST_CONNECTION_ERROR_MESSAGES.any? { |msg| exception.message.split(":").first =~ /^#{msg}/ } 158 @connection.real_connect(*@connection_options) 159 unless @logger 160 @connection.query(sql) 161 else 162 @logger.info "Retrying invalid statement with reopened connection" 163 log(sql, name) { @connection.query(sql) } 164 end 165 elsif exception.message.split(":").first =~ /Packets out of order/ 166 raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem update mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information." 198 if exception.message.split(":").first =~ /Packets out of order/ 199 raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings." 167 200 else 168 201 raise … … 292 325 293 326 private 327 def connect 328 encoding = @config[:encoding] 329 if encoding 330 begin 331 @connection.options(Mysql::SET_CHARSET_NAME, encoding) 332 rescue 333 raise ActiveRecord::ConnectionFailed, 'The :encoding option is only available for MySQL 4.1 and later with the mysql-ruby driver. Again, this does not work with the ruby-mysql driver or MySQL < 4.1.' 334 end 335 end 336 @connection.real_connect(*@connection_options) 337 execute("SET NAMES '#{encoding}'") if encoding 338 end 339 294 340 def select(sql, name = nil) 295 341 @connection.query_with_result = true branches/stable/activerecord/lib/active_record/connection_adapters/oci_adapter.rb
r3020 r3226 24 24 25 25 require 'active_record/connection_adapters/abstract_adapter' 26 require 'delegate' 26 27 27 28 begin … … 31 32 class Base 32 33 def self.oci_connection(config) #:nodoc: 33 conn = OCI8.new config[:username], config[:password], config[:host] 34 conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'} 35 conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} 36 conn.autocommit = true 37 ConnectionAdapters::OCIAdapter.new conn, logger 34 # Use OCI8AutoRecover instead of normal OCI8 driver. 35 ConnectionAdapters::OCIAdapter.new OCI8AutoRecover.new(config), logger 38 36 end 39 37 … … 119 117 time_array = ParseDate.parsedate value 120 118 time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1; 121 Time.send Base.default_timezone, *time_array119 Time.send(Base.default_timezone, *time_array) rescue nil 122 120 end 123 121 … … 214 212 215 213 214 # CONNECTION MANAGEMENT ====================================# 215 216 # Returns true if the connection is active. 217 def active? 218 # Pings the connection to check if it's still good. Note that an 219 # #active? method is also available, but that simply returns the 220 # last known state, which isn't good enough if the connection has 221 # gone stale since the last use. 222 @connection.ping 223 rescue OCIError 224 false 225 end 226 227 # Reconnects to the database. 228 def reconnect! 229 @connection.reset! 230 rescue OCIError => e 231 @logger.warn "#{adapter_name} automatic reconnection failed: #{e.message}" 232 end 233 234 216 235 # DATABASE STATEMENTS ====================================== 217 236 # … … 338 357 end 339 358 340 select_all(table_cols ).map do |row|359 select_all(table_cols, name).map do |row| 341 360 row['data_default'].sub!(/^'(.*)'\s*$/, '\1') if row['data_default'] 342 361 OCIColumn.new( … … 486 505 else define_a_column_pre_ar i 487 506 end 488 end 489 end 507 end 508 end 509 end 510 511 512 # The OCIConnectionFactory factors out the code necessary to connect and 513 # configure an OCI connection. 514 class OCIConnectionFactory 515 def new_connection(username, password, host) 516 conn = OCI8.new username, password, host 517 conn.exec %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'} 518 conn.exec %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} 519 conn.autocommit = true 520 conn 521 end 522 end 523 524 525 # The OCI8AutoRecover class enhances the OCI8 driver with auto-recover and 526 # reset functionality. If a call to #exec fails, and autocommit is turned on 527 # (ie., we're not in the middle of a longer transaction), it will 528 # automatically reconnect and try again. If autocommit is turned off, 529 # this would be dangerous (as the earlier part of the implied transaction 530 # may have failed silently if the connection died) -- so instead the 531 # connection is marked as dead, to be reconnected on it's next use. 532 class OCI8AutoRecover < DelegateClass(OCI8) 533 attr_accessor :active 534 alias :active? :active 535 536 cattr_accessor :auto_retry 537 class << self 538 alias :auto_retry? :auto_retry 539 end 540 @@auto_retry = false 541 542 def initialize(config, factory = OCIConnectionFactory.new) 543 @active = true 544 @username, @password, @host = config[:username], config[:password], config[:host] 545 @factory = factory 546 @connection = @factory.new_connection @username, @password, @host 547 super @connection 548 end 549 550 # Checks connection, returns true if active. Note that ping actively 551 # checks the connection, while #active? simply returns the last 552 # known state. 553 def ping 554 @connection.commit 555 @active = true 556 rescue 557 @active = false 558 raise 559 end 560 561 # Resets connection, by logging off and creating a new connection. 562 def reset! 563 logoff rescue nil 564 begin 565 @connection = @factory.new_connection @username, @password, @host 566 __setobj__ @connection 567 @active = true 568 rescue 569 @active = false 570 raise 571 end 572 end 573 574 # ORA-00028: your session has been killed 575 # ORA-01012: not logged on 576 # ORA-03113: end-of-file on communication channel 577 # ORA-03114: not connected to ORACLE 578 LOST_CONNECTION_ERROR_CODES = [ 28, 1012, 3113, 3114 ] 579 580 # Adds auto-recovery functionality. 581 # 582 # See: http://www.jiubao.org/ruby-oci8/api.en.html#label-11 583 def exec(sql, *bindvars) 584 should_retry = self.class.auto_retry? && autocommit? 585 586 begin 587 @connection.exec(sql, *bindvars) 588 rescue OCIError => e 589 raise unless LOST_CONNECTION_ERROR_CODES.include?(e.code) 590 @active = false 591 raise unless should_retry 592 should_retry = false 593 reset! rescue nil 594 retry 595 end 596 end 597 490 598 end 491 599 branches/stable/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
r2999 r3226 13 13 password = config[:password].to_s 14 14 15 encoding = config[:encoding]16 15 min_messages = config[:min_messages] 17 16 … … 23 22 24 23 pga = ConnectionAdapters::PostgreSQLAdapter.new( 25 PGconn.connect(host, port, "", "", database, username, password), logger 24 PGconn.connect(host, port, "", "", database, username, password), logger, config 26 25 ) 27 26 28 27 pga.schema_search_path = config[:schema_search_path] || config[:schema_order] 29 pga.execute("SET client_encoding TO '#{encoding}'") if encoding30 pga.execute("SET client_min_messages TO '#{min_messages}'") if min_messages31 28 32 29 pga … … 51 48 def adapter_name 52 49 'PostgreSQL' 50 end 51 52 def initialize(connection, logger, config = {}) 53 super(connection, logger) 54 @config = config 55 configure_connection 56 end 57 58 # Is this connection alive and ready for queries? 59 def active? 60 if @connection.respond_to?(:status) 61 @connection.status == PGconn::CONNECTION_OK 62 else 63 @connection.query 'SELECT 1' 64 true 65 end 66 rescue PGError 67 false 68 end 69 70 # Close then reopen the connection. 71 def reconnect! 72 # TODO: postgres-pr doesn't have PGconn#reset. 73 if @connection.respond_to?(:reset) 74 @connection.reset 75 configure_connection 76 end 53 77 end 54 78 … … 309 333 private 310 334 BYTEA_COLUMN_TYPE_OID = 17 335 336 def configure_connection 337 if @config[:encoding] 338 execute("SET client_encoding TO '#{@config[:encoding]}'") 339 end 340 if @config[:min_messages] 341 execute("SET client_min_messages TO '#{@config[:min_messages]}'") 342 end 343 end 311 344 312 345 def last_insert_id(table, sequence_name) branches/stable/activerecord/lib/active_record/connection_adapters/sqlserver_adapter.rb
r3185 r3226 31 31 raise ArgumentError, "Missing DSN. Argument ':dsn' must be set in order for this adapter to work." unless config.has_key?(:dsn) 32 32 dsn = config[:dsn] 33 conn = DBI.connect("DBI:ODBC:#{dsn}", username, password)33 driver_url = "DBI:ODBC:#{dsn}" 34 34 else 35 35 raise ArgumentError, "Missing Database. Argument ':database' must be set in order for this adapter to work." unless config.has_key?(:database) 36 36 database = config[:database] 37 37 host = config[:host] ? config[:host].to_s : 'localhost' 38 conn = DBI.connect("DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};") 39 end 38 driver_url = "DBI:ADO:Provider=SQLOLEDB;Data Source=#{host};Initial Catalog=#{database};User Id=#{username};Password=#{password};" 39 end 40 conn = DBI.connect(driver_url, username, password) 40 41 41 42 conn["AutoCommit"] = true 42 ConnectionAdapters::SQLServerAdapter.new(conn, logger )43 ConnectionAdapters::SQLServerAdapter.new(conn, logger, [driver_url, username, password]) 43 44 end 44 45 end # class Base … … 173 174 # [Linux strongmad 2.6.11-1.1369_FC4 #1 Thu Jun 2 22:55:56 EDT 2005 i686 i686 i386 GNU/Linux] 174 175 class SQLServerAdapter < AbstractAdapter 176 177 def initialize(connection, logger, connection_options=nil) 178 super(connection, logger) 179 @connection_options = connection_options 180 end 181 175 182 def native_database_types 176 183 { … … 195 202 def supports_migrations? #:nodoc: 196 203 true 204 end 205 206 # CONNECTION MANAGEMENT ====================================# 207 208 # Returns true if the connection is active. 209 def active? 210 @connection.execute("SELECT 1") { } 211 true 212 rescue DBI::DatabaseError, DBI::InterfaceError 213 false 214 end 215 216 # Reconnects to the database, returns false if no connection could be made. 217 def reconnect! 218 @connection.disconnect rescue nil 219 @connection = DBI.connect(*@connection_options) 220 rescue DBI::DatabaseError => e 221 @logger.warn "#{adapter_name} reconnection failed: #{e.message}" if @logger 222 false 197 223 end 198 224 branches/stable/activerecord/lib/active_record/fixtures.rb
r2862 r3226 509 509 ActiveRecord::Base.unlock_mutex 510 510 end 511 ActiveRecord::Base.clear_connection_cache! 511 512 end 512 513 branches/stable/railties/lib/dispatcher.rb
r2841 r3226 74 74 def reset_after_dispatch 75 75 reset_application! if Dependencies.load? 76 ActiveRecord::Base.clear_connection_cache! 76 77 Breakpoint.deactivate_drb if defined?(BREAKPOINT_SERVER_PORT) 77 78 end