From b3420f5a2e3c38e5efc2b3d995354c39af09569e Mon Sep 17 00:00:00 2001 From: Jonathan Viney Date: Sun, 31 Aug 2008 21:09:16 +1200 Subject: Implement savepoints. --- activerecord/test/cases/transactions_test.rb | 132 +++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) (limited to 'activerecord/test') diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index b12ec36455..27cc841dd3 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -213,6 +213,99 @@ class TransactionTest < ActiveRecord::TestCase assert Topic.find(2).approved?, "Second should still be approved" end + def test_invalid_keys_for_transaction + assert_raises ArgumentError do + Topic.transaction :forced => true do + end + end + end + + def test_force_savepoint_in_nested_transaction + Topic.transaction do + @first.approved = true + @second.approved = false + @first.save! + @second.save! + + begin + Topic.transaction :force => true do + @first.happy = false + @first.save! + raise + end + rescue + end + end + + assert @first.reload.approved? + assert !@second.reload.approved? + end + + def test_no_savepoint_in_nested_transaction_without_force + Topic.transaction do + @first.approved = true + @second.approved = false + @first.save! + @second.save! + + begin + Topic.transaction do + @first.approved = false + @first.save! + raise + end + rescue + end + end + + assert !@first.reload.approved? + assert !@second.reload.approved? + end + + def test_many_savepoints + Topic.transaction do + @first.content = "One" + @first.save! + + begin + Topic.transaction :force => true do + @first.content = "Two" + @first.save! + + begin + Topic.transaction :force => true do + @first.content = "Three" + @first.save! + + begin + Topic.transaction :force => true do + @first.content = "Four" + @first.save! + raise + end + rescue + end + + @three = @first.reload.content + raise + end + rescue + end + + @two = @first.reload.content + raise + end + rescue + end + + @one = @first.reload.content + end + + assert_equal "One", @one + assert_equal "Two", @two + assert_equal "Three", @three + end + uses_mocha 'mocking connection.commit_db_transaction' do def test_rollback_when_commit_raises Topic.connection.expects(:begin_db_transaction) @@ -282,6 +375,45 @@ class TransactionTest < ActiveRecord::TestCase end end +class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase + self.use_transactional_fixtures = true + fixtures :topics + + def test_automatic_savepoint_in_outer_transaction + @first = Topic.find(1) + + begin + Topic.transaction do + @first.approved = true + @first.save! + raise + end + rescue + assert !@first.reload.approved? + end + end + + def test_no_automatic_savepoint_for_inner_transaction + @first = Topic.find(1) + + Topic.transaction do + @first.approved = true + @first.save! + + begin + Topic.transaction do + @first.approved = false + @first.save! + raise + end + rescue + end + end + + assert !@first.reload.approved? + end +end + if current_adapter?(:PostgreSQLAdapter) class ConcurrentTransactionTest < TransactionTest use_concurrent_connections -- cgit v1.2.3 From b1b204abbb6d010284120598aec30cf5dcd096f6 Mon Sep 17 00:00:00 2001 From: Jonathan Viney Date: Sun, 31 Aug 2008 22:19:59 +1200 Subject: Fix what looks like a Mysql bug with transactions, savepoints, and create table. --- activerecord/test/cases/defaults_test.rb | 47 ++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 21 deletions(-) (limited to 'activerecord/test') diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index ee84cb8af8..3c4309753e 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -48,8 +48,33 @@ class DefaultTest < ActiveRecord::TestCase ensure klass.connection.drop_table(klass.table_name) rescue nil end + end + + if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter, :FirebirdAdapter, :OpenBaseAdapter, :OracleAdapter) + def test_default_integers + default = Default.new + assert_instance_of Fixnum, default.positive_integer + assert_equal 1, default.positive_integer + assert_instance_of Fixnum, default.negative_integer + assert_equal -1, default.negative_integer + assert_instance_of BigDecimal, default.decimal_number + assert_equal BigDecimal.new("2.78"), default.decimal_number + end + end + + if current_adapter?(:PostgreSQLAdapter) + def test_multiline_default_text + # older postgres versions represent the default with escapes ("\\012" for a newline) + assert ( "--- []\n\n" == Default.columns_hash['multiline_default'].default || + "--- []\\012\\012" == Default.columns_hash['multiline_default'].default) + end + end +end - +if current_adapter?(:MysqlAdapter) + class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase + self.use_transactional_fixtures = false + # MySQL uses an implicit default 0 rather than NULL unless in strict mode. # We use an implicit NULL so schema.rb is compatible with other databases. def test_mysql_integer_not_null_defaults @@ -77,24 +102,4 @@ class DefaultTest < ActiveRecord::TestCase klass.connection.drop_table(klass.table_name) rescue nil end end - - if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter, :FirebirdAdapter, :OpenBaseAdapter, :OracleAdapter) - def test_default_integers - default = Default.new - assert_instance_of Fixnum, default.positive_integer - assert_equal 1, default.positive_integer - assert_instance_of Fixnum, default.negative_integer - assert_equal -1, default.negative_integer - assert_instance_of BigDecimal, default.decimal_number - assert_equal BigDecimal.new("2.78"), default.decimal_number - end - end - - if current_adapter?(:PostgreSQLAdapter) - def test_multiline_default_text - # older postgres versions represent the default with escapes ("\\012" for a newline) - assert ( "--- []\n\n" == Default.columns_hash['multiline_default'].default || - "--- []\\012\\012" == Default.columns_hash['multiline_default'].default) - end - end end -- cgit v1.2.3 From 3f4a8b6d0a6c1ddab417b8d19affdc1e9f9209ba Mon Sep 17 00:00:00 2001 From: Jonathan Viney Date: Sun, 31 Aug 2008 22:34:54 +1200 Subject: Fix assert_queries failures by ignoring savepoint sql. --- activerecord/test/cases/helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/test') diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index f7bdac8013..0c03d29dec 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -31,7 +31,7 @@ rescue LoadError end ActiveRecord::Base.connection.class.class_eval do - IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/] + IGNORED_SQL = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/] def execute_with_query_record(sql, name = nil, &block) $queries_executed ||= [] -- cgit v1.2.3 From f48703e8c6ad8497795a67708ace4eb507a91119 Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Thu, 9 Oct 2008 14:35:42 +0200 Subject: Fix the final MySQL unit test failure that's related to savepoint support. --- activerecord/test/cases/defaults_test.rb | 67 +++++++++++++++++++------------- 1 file changed, 39 insertions(+), 28 deletions(-) (limited to 'activerecord/test') diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index 3c4309753e..875b7f8dbc 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -20,34 +20,7 @@ class DefaultTest < ActiveRecord::TestCase if current_adapter?(:MysqlAdapter) - #MySQL 5 and higher is quirky with not null text/blob columns. - #With MySQL Text/blob columns cannot have defaults. If the column is not null MySQL will report that the column has a null default - #but it behaves as though the column had a default of '' - def test_mysql_text_not_null_defaults - klass = Class.new(ActiveRecord::Base) - klass.table_name = 'test_mysql_text_not_null_defaults' - klass.connection.create_table klass.table_name do |t| - t.column :non_null_text, :text, :null => false - t.column :non_null_blob, :blob, :null => false - t.column :null_text, :text, :null => true - t.column :null_blob, :blob, :null => true - end - assert_equal '', klass.columns_hash['non_null_blob'].default - assert_equal '', klass.columns_hash['non_null_text'].default - - assert_equal nil, klass.columns_hash['null_blob'].default - assert_equal nil, klass.columns_hash['null_text'].default - - assert_nothing_raised do - instance = klass.create! - assert_equal '', instance.non_null_text - assert_equal '', instance.non_null_blob - assert_nil instance.null_text - assert_nil instance.null_blob - end - ensure - klass.connection.drop_table(klass.table_name) rescue nil - end + end if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter, :FirebirdAdapter, :OpenBaseAdapter, :OracleAdapter) @@ -73,8 +46,46 @@ end if current_adapter?(:MysqlAdapter) class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase + # ActiveRecord::Base#create! (and #save and other related methods) will + # open a new transaction. When in transactional fixtures mode, this will + # cause ActiveRecord to create a new savepoint. However, since MySQL doesn't + # support DDL transactions, creating a table will result in any created + # savepoints to be automatically released. This in turn causes the savepoint + # release code in AbstractAdapter#transaction to fail. + # + # We don't want that to happen, so we disable transactional fixtures here. self.use_transactional_fixtures = false + # MySQL 5 and higher is quirky with not null text/blob columns. + # With MySQL Text/blob columns cannot have defaults. If the column is not + # null MySQL will report that the column has a null default + # but it behaves as though the column had a default of '' + def test_mysql_text_not_null_defaults + klass = Class.new(ActiveRecord::Base) + klass.table_name = 'test_mysql_text_not_null_defaults' + klass.connection.create_table klass.table_name do |t| + t.column :non_null_text, :text, :null => false + t.column :non_null_blob, :blob, :null => false + t.column :null_text, :text, :null => true + t.column :null_blob, :blob, :null => true + end + assert_equal '', klass.columns_hash['non_null_blob'].default + assert_equal '', klass.columns_hash['non_null_text'].default + + assert_equal nil, klass.columns_hash['null_blob'].default + assert_equal nil, klass.columns_hash['null_text'].default + + assert_nothing_raised do + instance = klass.create! + assert_equal '', instance.non_null_text + assert_equal '', instance.non_null_blob + assert_nil instance.null_text + assert_nil instance.null_blob + end + ensure + klass.connection.drop_table(klass.table_name) rescue nil + end + # MySQL uses an implicit default 0 rather than NULL unless in strict mode. # We use an implicit NULL so schema.rb is compatible with other databases. def test_mysql_integer_not_null_defaults -- cgit v1.2.3 From e981eaaf342d06e399b5138553c964adcfadd87c Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Thu, 9 Oct 2008 14:52:02 +0200 Subject: Fix a stale typo in the PostgreSQL adapter. Fix a stale mock expection in transaction_test. --- activerecord/test/cases/transactions_test.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activerecord/test') diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 27cc841dd3..4bbcd272b7 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -309,7 +309,6 @@ class TransactionTest < ActiveRecord::TestCase uses_mocha 'mocking connection.commit_db_transaction' do def test_rollback_when_commit_raises Topic.connection.expects(:begin_db_transaction) - Topic.connection.expects(:transaction_active?).returns(true) if current_adapter?(:PostgreSQLAdapter) Topic.connection.expects(:commit_db_transaction).raises('OH NOES') Topic.connection.expects(:rollback_db_transaction) -- cgit v1.2.3 From 885c11b8f9e18f34b12076023455e72166365f00 Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Thu, 9 Oct 2008 15:41:56 +0200 Subject: Make SQLite3 pass the unit tests for savepoints. --- activerecord/test/cases/transactions_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/test') diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 4bbcd272b7..3c6bfd7e22 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -239,7 +239,7 @@ class TransactionTest < ActiveRecord::TestCase assert @first.reload.approved? assert !@second.reload.approved? - end + end if Topic.connection.supports_savepoints? def test_no_savepoint_in_nested_transaction_without_force Topic.transaction do @@ -260,7 +260,7 @@ class TransactionTest < ActiveRecord::TestCase assert !@first.reload.approved? assert !@second.reload.approved? - end + end if Topic.connection.supports_savepoints? def test_many_savepoints Topic.transaction do @@ -304,7 +304,7 @@ class TransactionTest < ActiveRecord::TestCase assert_equal "One", @one assert_equal "Two", @two assert_equal "Three", @three - end + end if Topic.connection.supports_savepoints? uses_mocha 'mocking connection.commit_db_transaction' do def test_rollback_when_commit_raises @@ -411,7 +411,7 @@ class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase assert !@first.reload.approved? end -end +end if Topic.connection.supports_savepoints? if current_adapter?(:PostgreSQLAdapter) class ConcurrentTransactionTest < TransactionTest -- cgit v1.2.3 From e916aa7ea1613be966959c05ad41d13fee55a683 Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Thu, 9 Oct 2008 16:24:15 +0200 Subject: Rename ActiveRecord::Base#transaction's :force option to :nest. Improve documentation for nested transactions. --- activerecord/test/cases/transactions_test.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'activerecord/test') diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 3c6bfd7e22..069ba9d6c5 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -215,7 +215,7 @@ class TransactionTest < ActiveRecord::TestCase def test_invalid_keys_for_transaction assert_raises ArgumentError do - Topic.transaction :forced => true do + Topic.transaction :nested => true do end end end @@ -228,7 +228,7 @@ class TransactionTest < ActiveRecord::TestCase @second.save! begin - Topic.transaction :force => true do + Topic.transaction :nest => true do @first.happy = false @first.save! raise @@ -268,17 +268,17 @@ class TransactionTest < ActiveRecord::TestCase @first.save! begin - Topic.transaction :force => true do + Topic.transaction :nest => true do @first.content = "Two" @first.save! begin - Topic.transaction :force => true do + Topic.transaction :nest => true do @first.content = "Three" @first.save! begin - Topic.transaction :force => true do + Topic.transaction :nest => true do @first.content = "Four" @first.save! raise -- cgit v1.2.3 From fb2325e35855d62abd2c76ce03feaa3ca7992e4f Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Thu, 9 Oct 2008 17:57:49 +0200 Subject: Reimplement Jeremy's PostgreSQL automatic transaction state introspection code. - Fixed compatibility with the old 'postgres' driver which doesn't support transaction state introspection. - Added unit tests for it. --- activerecord/test/cases/transactions_test.rb | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'activerecord/test') diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 069ba9d6c5..0c69fee8f2 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -310,6 +310,7 @@ class TransactionTest < ActiveRecord::TestCase def test_rollback_when_commit_raises Topic.connection.expects(:begin_db_transaction) Topic.connection.expects(:commit_db_transaction).raises('OH NOES') + Topic.connection.expects(:outside_transaction?).returns(false) Topic.connection.expects(:rollback_db_transaction) assert_raise RuntimeError do @@ -319,6 +320,39 @@ class TransactionTest < ActiveRecord::TestCase end end end + + if current_adapter?(:PostgreSQLAdapter) && PGconn.public_method_defined?(:transaction_status) + def test_outside_transaction_works + Topic.logger.info("-------------") + assert Topic.connection.outside_transaction? + Topic.connection.begin_db_transaction + assert !Topic.connection.outside_transaction? + Topic.connection.rollback_db_transaction + assert Topic.connection.outside_transaction? + end + + uses_mocha 'mocking connection.rollback_db_transaction' do + def test_rollback_wont_be_executed_if_no_transaction_active + assert_raise RuntimeError do + Topic.transaction do + Topic.connection.rollback_db_transaction + Topic.connection.expects(:rollback_db_transaction).never + raise "Rails doesn't scale!" + end + end + end + end + + def test_open_transactions_count_is_reset_to_zero_if_no_transaction_active + Topic.transaction do + Topic.transaction do + Topic.connection.rollback_db_transaction + end + assert_equal 0, Topic.connection.open_transactions + end + assert_equal 0, Topic.connection.open_transactions + end + end def test_sqlite_add_column_in_transaction_raises_statement_invalid return true unless current_adapter?(:SQLite3Adapter, :SQLiteAdapter) -- cgit v1.2.3 From ab0ce052ba23a4cce7a84ecade0d00d9cc518ebd Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 10 Jan 2009 13:36:09 -0800 Subject: Introduce transaction_joinable flag to mark that the fixtures transaction can't joined, a new savepoint is required even if :requires_new is not set. Use :requires_new option instead of :nest. Update changelog. [#383 state:committed] --- activerecord/test/cases/transactions_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/test') diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 0c69fee8f2..f07fad1828 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -228,7 +228,7 @@ class TransactionTest < ActiveRecord::TestCase @second.save! begin - Topic.transaction :nest => true do + Topic.transaction :requires_new => true do @first.happy = false @first.save! raise @@ -268,17 +268,17 @@ class TransactionTest < ActiveRecord::TestCase @first.save! begin - Topic.transaction :nest => true do + Topic.transaction :requires_new => true do @first.content = "Two" @first.save! begin - Topic.transaction :nest => true do + Topic.transaction :requires_new => true do @first.content = "Three" @first.save! begin - Topic.transaction :nest => true do + Topic.transaction :requires_new => true do @first.content = "Four" @first.save! raise -- cgit v1.2.3