Skip to content
This repository
Browse code

Reimplement Jeremy's PostgreSQL automatic transaction state introspec…

…tion code.

- Fixed compatibility with the old 'postgres' driver which doesn't support
  transaction state introspection.
- Added unit tests for it.
  • Loading branch information...
commit fb2325e35855d62abd2c76ce03feaa3ca7992e4f 1 parent e916aa7
Hongli Lai FooBarWidget authored
20 activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -53,6 +53,20 @@ def update(sql, name = nil)
53 53 def delete(sql, name = nil)
54 54 delete_sql(sql, name)
55 55 end
  56 +
  57 + # Checks whether there is currently no transaction active. This is done
  58 + # by querying the database driver, and does not use the transaction
  59 + # house-keeping information recorded by #increment_open_transactions and
  60 + # friends.
  61 + #
  62 + # Returns true if there is no transaction active, false if there is a
  63 + # transaction active, and nil if this information is unknown.
  64 + #
  65 + # Not all adapters supports transaction state introspection. Currently,
  66 + # only the PostgreSQL adapter supports this.
  67 + def outside_transaction?
  68 + nil
  69 + end
56 70
57 71 # Runs the given block in a database transaction, and returns the result
58 72 # of the block.
@@ -119,7 +133,7 @@ def transaction(start_db_transaction = false)
119 133 yield
120 134 end
121 135 rescue Exception => database_transaction_rollback
122   - if transaction_open
  136 + if transaction_open && !outside_transaction?
123 137 transaction_open = false
124 138 decrement_open_transactions
125 139 if open_transactions == 0
@@ -131,7 +145,9 @@ def transaction(start_db_transaction = false)
131 145 raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
132 146 end
133 147 ensure
134   - if transaction_open
  148 + if outside_transaction?
  149 + @open_transactions = 0
  150 + elsif transaction_open
135 151 decrement_open_transactions
136 152 begin
137 153 if open_transactions == 0
10 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -532,6 +532,16 @@ def commit_db_transaction
532 532 def rollback_db_transaction
533 533 execute "ROLLBACK"
534 534 end
  535 +
  536 + if PGconn.public_method_defined?(:transaction_status)
  537 + # ruby-pg defines Ruby constants for transaction status,
  538 + # ruby-postgres does not.
  539 + PQTRANS_IDLE = defined?(PGconn::PQTRANS_IDLE) ? PGconn::PQTRANS_IDLE : 0
  540 +
  541 + def outside_transaction?
  542 + @connection.transaction_status == PQTRANS_IDLE
  543 + end
  544 + end
535 545
536 546 def create_savepoint
537 547 execute("SAVEPOINT #{current_savepoint_name}")
34 activerecord/test/cases/transactions_test.rb
@@ -310,6 +310,7 @@ def test_many_savepoints
310 310 def test_rollback_when_commit_raises
311 311 Topic.connection.expects(:begin_db_transaction)
312 312 Topic.connection.expects(:commit_db_transaction).raises('OH NOES')
  313 + Topic.connection.expects(:outside_transaction?).returns(false)
313 314 Topic.connection.expects(:rollback_db_transaction)
314 315
315 316 assert_raise RuntimeError do
@@ -319,6 +320,39 @@ def test_rollback_when_commit_raises
319 320 end
320 321 end
321 322 end
  323 +
  324 + if current_adapter?(:PostgreSQLAdapter) && PGconn.public_method_defined?(:transaction_status)
  325 + def test_outside_transaction_works
  326 + Topic.logger.info("-------------")
  327 + assert Topic.connection.outside_transaction?
  328 + Topic.connection.begin_db_transaction
  329 + assert !Topic.connection.outside_transaction?
  330 + Topic.connection.rollback_db_transaction
  331 + assert Topic.connection.outside_transaction?
  332 + end
  333 +
  334 + uses_mocha 'mocking connection.rollback_db_transaction' do
  335 + def test_rollback_wont_be_executed_if_no_transaction_active
  336 + assert_raise RuntimeError do
  337 + Topic.transaction do
  338 + Topic.connection.rollback_db_transaction
  339 + Topic.connection.expects(:rollback_db_transaction).never
  340 + raise "Rails doesn't scale!"
  341 + end
  342 + end
  343 + end
  344 + end
  345 +
  346 + def test_open_transactions_count_is_reset_to_zero_if_no_transaction_active
  347 + Topic.transaction do
  348 + Topic.transaction do
  349 + Topic.connection.rollback_db_transaction
  350 + end
  351 + assert_equal 0, Topic.connection.open_transactions
  352 + end
  353 + assert_equal 0, Topic.connection.open_transactions
  354 + end
  355 + end
322 356
323 357 def test_sqlite_add_column_in_transaction_raises_statement_invalid
324 358 return true unless current_adapter?(:SQLite3Adapter, :SQLiteAdapter)

0 comments on commit fb2325e

Please sign in to comment.
Something went wrong with that request. Please try again.