Ticket #5457: nested_transactions_with_implicit_transaction_config.2.patch
| File nested_transactions_with_implicit_transaction_config.2.patch, 15.0 kB (added by tarmo, 1 year ago) |
|---|
-
a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
old new 48 48 end 49 49 50 50 # Wrap a block in a transaction. Returns result of block. 51 def transaction(start_db_transaction = true )51 def transaction(start_db_transaction = true, savepoint_number = 1) 52 52 transaction_open = false 53 savepoint_open = false 53 54 begin 54 55 if block_given? 55 56 if start_db_transaction 56 begin_db_transaction 57 if savepoint_number == 1 58 begin_db_transaction 59 else 60 if create_savepoint(savepoint_number) 61 savepoint_open = true 62 end 63 end 57 64 transaction_open = true 58 65 end 59 66 yield … … 61 68 rescue Exception => database_transaction_rollback 62 69 if transaction_open 63 70 transaction_open = false 64 rollback_db_transaction 71 unless savepoint_open 72 rollback_db_transaction 73 else 74 savepoint_open = false 75 rollback_to_savepoint(savepoint_number) 76 end 65 77 end 66 78 raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback 67 79 end 68 80 ensure 69 81 if transaction_open 70 82 begin 71 commit_db_transaction 83 unless savepoint_open 84 commit_db_transaction 85 else 86 release_savepoint(savepoint_number) 87 end 72 88 rescue Exception => database_transaction_rollback 73 rollback_db_transaction 89 unless savepoint_open 90 rollback_db_transaction 91 else 92 rollback_to_savepoint(savepoint_number) 93 end 74 94 raise 75 95 end 76 96 end … … 86 106 # done if the transaction block raises an exception or returns false. 87 107 def rollback_db_transaction() end 88 108 109 # abstract create_savepoint method that does nothing 110 def create_savepoint(sp_number) 111 end 112 113 # abstract rollback_to_savepoint method that does nothing 114 def rollback_to_savepoint(sp_number) 115 end 116 117 # abstract release_savepoint method that does nothing 118 def release_savepoint(sp_number) 119 end 120 89 121 # Alias for #add_limit_offset!. 90 122 def add_limit!(sql, options) 91 123 add_limit_offset!(sql, options) if options -
a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
old new 284 284 # Transactions aren't supported 285 285 end 286 286 287 def create_savepoint(sp_number) 288 execute("SAVEPOINT rails_nested_transaction_#{sp_number}") 289 return true 290 rescue Exception 291 # savepoints are not supported 292 end 293 294 def rollback_to_savepoint(sp_number) 295 execute("ROLLBACK TO SAVEPOINT rails_nested_transaction_#{sp_number}") 296 rescue Exception 297 # savepoints are not supported 298 end 299 300 def release_savepoint(sp_number) 301 execute("RELEASE SAVEPOINT rails_nested_transaction_#{sp_number}") 302 rescue Exception 303 # savepoints are not supported 304 end 305 287 306 288 307 def add_limit_offset!(sql, options) #:nodoc: 289 308 if limit = options[:limit] -
a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
old new 417 417 execute "ROLLBACK" 418 418 end 419 419 420 def create_savepoint(sp_number) 421 execute("SAVEPOINT rails_nested_transaction_#{sp_number}") 422 return true 423 rescue Exception 424 # savepoints are not supported 425 end 426 427 def rollback_to_savepoint(sp_number) 428 execute("ROLLBACK TO SAVEPOINT rails_nested_transaction_#{sp_number}") 429 rescue Exception 430 # savepoints are not supported 431 end 432 433 def release_savepoint(sp_number) 434 execute("RELEASE SAVEPOINT rails_nested_transaction_#{sp_number}") 435 rescue Exception 436 # savepoints are not supported 437 end 438 420 439 # SCHEMA STATEMENTS ======================================== 421 440 422 441 # Returns the list of all tables in the schema search path or a specified schema. -
a/activerecord/lib/active_record/fixtures.rb
old new 557 557 load_fixtures 558 558 @@already_loaded_fixtures[self.class] = @loaded_fixtures 559 559 end 560 Thread.current['transactional_test'] = true 560 561 ActiveRecord::Base.send :increment_open_transactions 561 562 ActiveRecord::Base.connection.begin_db_transaction 562 563 … … 576 577 return unless defined?(ActiveRecord::Base) && !ActiveRecord::Base.configurations.blank? 577 578 578 579 # Rollback changes if a transaction is active. 579 if use_transactional_fixtures? && Thread.current['open_transactions'] != 0 580 ActiveRecord::Base.connection.rollback_db_transaction 581 Thread.current['open_transactions'] = 0 580 if use_transactional_fixtures? 581 if Thread.current['open_transactions'] != 0 582 ActiveRecord::Base.connection.rollback_db_transaction 583 Thread.current['open_transactions'] = 0 584 end 585 Thread.current['transactional_test'] = false 582 586 end 583 587 ActiveRecord::Base.verify_active_connections! 584 588 end -
a/activerecord/lib/active_record/transactions.rb
old new 69 69 # Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you 70 70 # should be ready to catch those in your application code. 71 71 module ClassMethods 72 def transaction( &block)72 def transaction(options={}, &block) 73 73 previous_handler = trap('TERM') { raise TransactionError, "Transaction aborted" } 74 74 increment_open_transactions 75 75 76 76 begin 77 connection.transaction( Thread.current['start_db_transaction'], &block)77 connection.transaction((options[:force] == true) || Thread.current['start_db_transaction'], Thread.current['open_transactions'], &block) 78 78 ensure 79 79 decrement_open_transactions 80 80 trap('TERM', previous_handler) 81 81 end 82 82 end 83 83 84 # Sets the options for implicit transactions. For different 85 # action types. 86 # 87 # The action types are: 88 # * <tt>:save</tt> - transaction type for creating or updating a record. 89 # * <tt>:destroy</tt> - transaction type for deleting a record. 90 # 91 # The transaction types are: 92 # * <tt>:none</tt> - no transaction is created. 93 # * <tt>:flat</tt> - transaction is only created if non exist. This is the default. 94 # * <tt>:nested</tt> - transaction is created even if one exists. This only works if the database supports nested transactions, if it does not then the behaviour is the same as for :flat. 95 # 96 # Option examples: 97 # set_transaction_types :save => :flat 98 # set_transaction_types :save => :none, :destroy => :nested 99 # set_transaction_types :nested 100 def set_transaction_types(options) 101 case options 102 when Symbol 103 options = { :save => options, :destroy => options } 104 when Hash 105 options[:save] ||= :flat 106 options[:destroy] ||= :flat 107 else 108 raise(ArgumentError, "Invalid transaction type(s): %s", options.inspect) 109 end 110 111 options.assert_valid_keys(:save, :destroy) 112 113 write_inheritable_attribute("transaction_types", options) 114 end 115 116 def get_transaction_type(action_type) 117 get_transaction_types[action_type] || :flat 118 end 119 120 def get_transaction_types 121 (read_inheritable_attribute("transaction_types") or write_inheritable_attribute("transaction_types", {})) 122 end 123 84 124 private 85 125 def increment_open_transactions #:nodoc: 86 126 open = Thread.current['open_transactions'] ||= 0 … … 93 133 end 94 134 end 95 135 96 def transaction( &block)97 self.class.transaction( &block)136 def transaction(options={}, &block) 137 self.class.transaction(options, &block) 98 138 end 99 139 100 140 def destroy_with_transactions #:nodoc: 101 transaction { destroy_without_transactions } 141 transaction_type = self.class.get_transaction_type(:destroy) 142 if transaction_type == :none 143 destroy_without_transactions 144 else 145 options = { :force => (transaction_type == :nested) } 146 transaction(options) { destroy_without_transactions } 147 end 102 148 end 103 149 104 150 def save_with_transactions(perform_validation = true) #:nodoc: 105 rollback_active_record_state! { transaction { save_without_transactions(perform_validation) } } 151 rollback_active_record_state! do 152 transaction_type = self.class.get_transaction_type(:save) 153 if transaction_type == :none 154 save_without_transactions 155 else 156 options = { :force => (transaction_type == :nested) } 157 transaction(options) { save_without_transactions(perform_validation) } 158 end 159 end 106 160 end 107 161 108 162 def save_with_transactions! #:nodoc: 109 rollback_active_record_state! { transaction { save_without_transactions! } } 163 rollback_active_record_state! do 164 transaction_type = self.class.get_transaction_type(:save) 165 if transaction_type == :none 166 save_without_transactions! 167 else 168 options = { :force => (transaction_type == :nested) } 169 transaction(options) { save_without_transactions! } 170 end 171 end 110 172 end 111 173 112 174 # Reset id and @new_record if the transaction rolls back. -
a/activerecord/test/abstract_unit.rb
old new 63 63 ActiveRecord::Base.connection.class.class_eval do 64 64 65 65 if not (const_get('IGNORED_SQL') rescue nil) 66 IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/ ]66 IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SHOW FIELDS/] 67 67 68 68 def execute_with_counting(sql, name = nil, &block) 69 69 $query_count ||= 0 -
a/activerecord/test/transactions_test.rb
old new 279 279 end 280 280 end 281 281 end 282 283 if current_adapter?(:PostgreSQLAdapter) or current_adapter?(:MysqlAdapter) 284 class NestedTransactionsTest < TransactionTest 285 286 def test_nested_explicit_transactions_with_forced_nesting 287 Topic.transaction do 288 @first.approved = true 289 @first.save 290 @second.approved = true 291 @second.save 292 begin 293 Topic.transaction :force => true do 294 @second.approved = false 295 @second.save 296 raise "Bad things!" 297 end 298 rescue 299 # ignore the exception 300 end 301 end 302 303 assert Topic.find(1).approved?, "First should have been approved" 304 assert Topic.find(2).approved?, "Second should have been approved" 305 end 306 307 def test_nested_explicit_transactions_without_forced_nesting 308 Topic.transaction do 309 @first.approved = true 310 @first.save 311 @second.approved = true 312 @second.save 313 begin 314 Topic.transaction do 315 @second.approved = false 316 @second.save 317 raise "Bad things!" 318 end 319 rescue 320 # ignore the exception 321 end 322 end 323 324 assert Topic.find(1).approved?, "First should have been approved" 325 assert !Topic.find(2).approved?, "Second should have been unapproved" 326 end 327 328 def test_transaction_type_save_none 329 with_topic_transaction_options :save => :none do 330 assert_queries(1) { @first.save! } 331 end 332 end 333 334 def test_transaction_type_save_flat 335 with_topic_transaction_options :save => :flat do 336 assert_queries(3) { @first.save! } 337 end 338 end 339 340 def test_transaction_type_save_flat_inside_transaction 341 with_topic_transaction_options :save => :flat do 342 Topic.transaction do 343 assert_queries(1) { @first.save! } 344 end 345 end 346 end 347 348 def test_transaction_type_save_nested 349 with_topic_transaction_options :save => :nested do 350 assert_queries(3) { @first.save! } 351 end 352 end 353 354 def test_transaction_type_save_nested_inside_transaction 355 with_topic_transaction_options :save => :nested do 356 Topic.transaction do 357 assert_queries(3) { @first.save! } 358 end 359 end 360 end 361 362 def test_transaction_type_destroy_none 363 with_topic_transaction_options :destroy => :none do 364 assert_queries(8) { assert @first.destroy } 365 end 366 end 367 368 def test_transaction_type_destroy_flat 369 with_topic_transaction_options :destroy => :flat do 370 assert_queries(10) { assert @first.destroy } 371 end 372 end 373 374 def test_transaction_type_destroy_nested 375 with_topic_transaction_options :destroy => :nested do 376 assert_queries(12) { assert @first.destroy } 377 end 378 end 379 380 private 381 def with_topic_transaction_options(options) 382 # NOTE Reply does not inherit this because the class 383 # has already been loaded 384 old_types_topic = Topic.send(:get_transaction_types) 385 old_types_reply = Topic.send(:get_transaction_types) 386 387 begin 388 Topic.send(:set_transaction_types, options) 389 Reply.send(:set_transaction_types, options) 390 yield 391 ensure 392 Topic.send(:set_transaction_types, old_types_topic) 393 Reply.send(:set_transaction_types, old_types_reply) 394 end 395 end 396 end 397 end