diff options
11 files changed, 85 insertions, 40 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 217eada1d7..40a26d98e1 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,8 @@ +* Add new error class `QueryCanceled` which will be raised + when canceling statement due to user request. + + *Ryuta Kamizono* + * Add `#up_only` to database migrations for code that is only relevant when migrating up, e.g. populating a new column. @@ -190,7 +195,7 @@ *Jeremy Green* -* Add new error class `TransactionTimeout` which will be raised +* Add new error class `LockWaitTimeout` which will be raised when lock wait timeout exceeded. *Gabriel Courtemanche* diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 0e552dba95..ede8a9c1e2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -635,6 +635,7 @@ module ActiveRecord ER_CANNOT_ADD_FOREIGN = 1215 ER_CANNOT_CREATE_TABLE = 1005 ER_LOCK_WAIT_TIMEOUT = 1205 + ER_QUERY_INTERRUPTED = 1317 ER_QUERY_TIMEOUT = 3024 def translate_exception(exception, message) @@ -660,9 +661,11 @@ module ActiveRecord when ER_LOCK_DEADLOCK Deadlocked.new(message) when ER_LOCK_WAIT_TIMEOUT - TransactionTimeout.new(message) + LockWaitTimeout.new(message) when ER_QUERY_TIMEOUT StatementTimeout.new(message) + when ER_QUERY_INTERRUPTED + QueryCanceled.new(message) else super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 1739c288b6..7b27f6b7a0 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -418,9 +418,9 @@ module ActiveRecord when DEADLOCK_DETECTED Deadlocked.new(message) when LOCK_NOT_AVAILABLE - TransactionTimeout.new(message) + LockWaitTimeout.new(message) when QUERY_CANCELED - StatementTimeout.new(message) + QueryCanceled.new(message) else super end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 7382879fce..efcbd44776 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -335,14 +335,18 @@ module ActiveRecord class IrreversibleOrderError < ActiveRecordError end - # TransactionTimeout will be raised when lock wait timeout exceeded. - class TransactionTimeout < StatementInvalid + # LockWaitTimeout will be raised when lock wait timeout exceeded. + class LockWaitTimeout < StatementInvalid end # StatementTimeout will be raised when statement timeout exceeded. class StatementTimeout < StatementInvalid end + # QueryCanceled will be raised when canceling statement due to user request. + class QueryCanceled < StatementInvalid + end + # UnknownAttributeReference is raised when an unknown and potentially unsafe # value is passed to a query method when allow_unsafe_raw_sql is set to # :disabled. For example, passing a non column name value to a relation's diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d3b8091665..081ef5771f 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -544,7 +544,6 @@ module ActiveRecord end @records.each(&:readonly!) if readonly_value - @offsets = {} unless @offsets.empty? @loaded = true @records diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb index 4a3a4503de..cb183cc54c 100644 --- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb +++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb @@ -60,8 +60,8 @@ module ActiveRecord end end - test "raises TransactionTimeout when lock wait timeout exceeded" do - assert_raises(ActiveRecord::TransactionTimeout) do + test "raises LockWaitTimeout when lock wait timeout exceeded" do + assert_raises(ActiveRecord::LockWaitTimeout) do s = Sample.create!(value: 1) latch1 = Concurrent::CountDownLatch.new latch2 = Concurrent::CountDownLatch.new @@ -116,5 +116,32 @@ module ActiveRecord end end end + + test "raises QueryCanceled when canceling statement due to user request" do + assert_raises(ActiveRecord::QueryCanceled) do + s = Sample.create!(value: 1) + latch = Concurrent::CountDownLatch.new + + thread = Thread.new do + Sample.transaction do + Sample.lock.find(s.id) + latch.count_down + sleep(0.5) + conn = Sample.connection + pid = conn.query_value("SELECT id FROM information_schema.processlist WHERE info LIKE '% FOR UPDATE'") + conn.execute("KILL QUERY #{pid}") + end + end + + begin + Sample.transaction do + latch.wait + Sample.lock.find(s.id) + end + ensure + thread.join + end + end + end end end diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb index 4d63bbce59..c24dfeb345 100644 --- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb +++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb @@ -91,9 +91,9 @@ module ActiveRecord end end - test "raises TransactionTimeout when lock wait timeout exceeded" do + test "raises LockWaitTimeout when lock wait timeout exceeded" do skip unless ActiveRecord::Base.connection.postgresql_version >= 90300 - assert_raises(ActiveRecord::TransactionTimeout) do + assert_raises(ActiveRecord::LockWaitTimeout) do s = Sample.create!(value: 1) latch1 = Concurrent::CountDownLatch.new latch2 = Concurrent::CountDownLatch.new @@ -120,8 +120,8 @@ module ActiveRecord end end - test "raises StatementTimeout when statement timeout exceeded" do - assert_raises(ActiveRecord::StatementTimeout) do + test "raises QueryCanceled when statement timeout exceeded" do + assert_raises(ActiveRecord::QueryCanceled) do s = Sample.create!(value: 1) latch1 = Concurrent::CountDownLatch.new latch2 = Concurrent::CountDownLatch.new @@ -148,6 +148,33 @@ module ActiveRecord end end + test "raises QueryCanceled when canceling statement due to user request" do + assert_raises(ActiveRecord::QueryCanceled) do + s = Sample.create!(value: 1) + latch = Concurrent::CountDownLatch.new + + thread = Thread.new do + Sample.transaction do + Sample.lock.find(s.id) + latch.count_down + sleep(0.5) + conn = Sample.connection + pid = conn.query_value("SELECT pid FROM pg_stat_activity WHERE query LIKE '% FOR UPDATE'") + conn.execute("SELECT pg_cancel_backend(#{pid})") + end + end + + begin + Sample.transaction do + latch.wait + Sample.lock.find(s.id) + end + ensure + thread.join + end + end + end + private def with_warning_suppression diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 93957ff50f..e936c56ab8 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -691,22 +691,6 @@ class FinderTest < ActiveRecord::TestCase assert_kind_of Array, Topic.last(5) end - def test_first_should_respect_loaded_records - authors = Author.order(:name) - - assert_equal authors(:bob), authors.first - - aaron = authors.create!(name: "Aaron") - - authors.load - - assert_no_queries do - assert_equal aaron, authors.first - assert_equal authors(:bob), authors.second - assert_not_equal authors.first, authors.second - end - end - def test_unexisting_record_exception_handling assert_raise(ActiveRecord::RecordNotFound) { Topic.find(1).parent diff --git a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt index 0eb9d82bbb..518cb1121e 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt +++ b/railties/lib/rails/generators/erb/scaffold/templates/_form.html.erb.tt @@ -15,15 +15,15 @@ <div class="field"> <% if attribute.password_digest? -%> <%%= form.label :password %> - <%%= form.password_field :password, id: :<%= field_id(:password) %> %> + <%%= form.password_field :password %> </div> <div class="field"> <%%= form.label :password_confirmation %> - <%%= form.password_field :password_confirmation, id: :<%= field_id(:password_confirmation) %> %> + <%%= form.password_field :password_confirmation %> <% else -%> <%%= form.label :<%= attribute.column_name %> %> - <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, id: :<%= field_id(attribute.column_name) %> %> + <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %> %> <% end -%> </div> diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 99165168fd..98fcc95964 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -114,10 +114,6 @@ module Rails "new_#{singular_route_name}_url" end - def field_id(attribute_name) - [singular_table_name, attribute_name].join("_") - end - def singular_table_name # :doc: @singular_table_name ||= (pluralize_table_names? ? table_name.singularize : table_name) end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index b6294c3b94..29426cd99f 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -471,8 +471,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end assert_file "app/views/accounts/_form.html.erb" do |content| - assert_match(/^\W{4}<%= form\.text_field :name, id: :account_name %>/, content) - assert_match(/^\W{4}<%= form\.text_field :currency_id, id: :account_currency_id %>/, content) + assert_match(/^\W{4}<%= form\.text_field :name %>/, content) + assert_match(/^\W{4}<%= form\.text_field :currency_id %>/, content) end end @@ -495,8 +495,8 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase end assert_file "app/views/users/_form.html.erb" do |content| - assert_match(/<%= form\.password_field :password, id: :user_password %>/, content) - assert_match(/<%= form\.password_field :password_confirmation, id: :user_password_confirmation %>/, content) + assert_match(/<%= form\.password_field :password %>/, content) + assert_match(/<%= form\.password_field :password_confirmation %>/, content) end assert_file "app/views/users/index.html.erb" do |content| |