Skip to content

Commit fc01241

Browse files
authored
Merge pull request rails#54454 from skipkayhil/hm-instrument-allow-retry
Add `allow_retry` to `sql.active_record`
2 parents c2d8c12 + 48ee7db commit fc01241

File tree

5 files changed

+44
-59
lines changed

5 files changed

+44
-59
lines changed

activerecord/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
* Add `allow_retry` to `sql.active_record` instrumentation.
2+
3+
This enables identifying queries which are and are not automatically
4+
retryable on connection errors.
5+
6+
*Hartley McGuire*
7+
18
* Better support UPDATE with JOIN for Postgresql and SQLite3
29

310
Previously when generating update queries with one or more JOIN clauses,

activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ def internal_exec_query(...) # :nodoc:
551551
# Lowest level way to execute a query. Doesn't check for illegal writes, doesn't annotate queries, yields a native result object.
552552
def raw_execute(sql, name = nil, binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true, batch: false)
553553
type_casted_binds = type_casted_binds(binds)
554-
log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
554+
log(sql, name, binds, type_casted_binds, async: async, allow_retry: allow_retry) do |notification_payload|
555555
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
556556
result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
557557
perform_query(conn, sql, binds, type_casted_binds, prepare: prepare, notification_payload: notification_payload, batch: batch)

activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1131,14 +1131,15 @@ def translate_exception_class(native_error, sql, binds)
11311131
active_record_error
11321132
end
11331133

1134-
def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block) # :doc:
1134+
def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, allow_retry: false, &block) # :doc:
11351135
instrumenter.instrument(
11361136
"sql.active_record",
11371137
sql: sql,
11381138
name: name,
11391139
binds: binds,
11401140
type_casted_binds: type_casted_binds,
11411141
async: async,
1142+
allow_retry: allow_retry,
11421143
connection: self,
11431144
transaction: current_transaction.user_transaction.presence,
11441145
affected_rows: 0,

activerecord/test/cases/adapter_test.rb

Lines changed: 32 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -700,74 +700,49 @@ def teardown
700700
assert_predicate @connection, :active?
701701
end
702702

703-
test "idempotent SELECT queries are retried and result in a reconnect" do
704-
Post.first
705-
706-
remote_disconnect @connection
707-
708-
assert Post.first
709-
assert_predicate @connection, :active?
710-
711-
remote_disconnect @connection
712-
713-
assert Post.where(id: [1, 2]).first
714-
assert_predicate @connection, :active?
715-
end
716-
717-
test "#find and #find_by queries with known attributes are retried and result in a reconnect" do
718-
Post.first
719-
720-
remote_disconnect @connection
721-
722-
assert Post.find(1)
723-
assert_predicate @connection, :active?
724-
725-
remote_disconnect @connection
726-
727-
assert Post.find_by(title: "Welcome to the weblog")
728-
assert_predicate @connection, :active?
729-
end
730-
731-
test "#exists? queries are retried and result in a reconnect" do
732-
Post.first
733-
734-
remote_disconnect @connection
735-
736-
assert_predicate Post, :exists?
737-
assert_predicate @connection, :active?
703+
test "idempotent SELECT queries allow retries" do
704+
notifications = capture_notifications("sql.active_record") do
705+
assert Post.first
706+
assert Post.where(id: [1, 2]).first
707+
assert Post.find(1)
708+
assert Post.find_by(title: "Welcome to the weblog")
709+
assert_predicate Post, :exists?
710+
end.select { |n| n.payload[:name] != "SCHEMA" }
711+
712+
assert_equal 5, notifications.length
713+
714+
notifications.each do |n|
715+
assert n.payload[:allow_retry]
716+
end
738717
end
739718

740-
test "queries containing SQL fragments are not retried" do
741-
Post.first
719+
test "queries containing SQL fragments do not allow retries" do
720+
notifications = capture_notifications("sql.active_record") do
721+
Post.where("1 = 1").to_a
722+
Post.select("title AS custom_title").first
723+
Book.find_by("updated_at < ?", 2.weeks.ago)
724+
end.select { |n| n.payload[:name] != "SCHEMA" }
742725

743-
remote_disconnect @connection
744-
745-
assert_raises(ActiveRecord::ConnectionFailed) { Post.where("1 = 1").to_a }
746-
assert_not_predicate @connection, :active?
726+
assert_equal 3, notifications.length
747727

748-
remote_disconnect @connection
749-
750-
assert_raises(ActiveRecord::ConnectionFailed) { Post.select("title AS custom_title").first }
751-
assert_not_predicate @connection, :active?
752-
753-
remote_disconnect @connection
754-
755-
assert_raises(ActiveRecord::ConnectionFailed) { Post.find_by("updated_at < ?", 2.weeks.ago) }
756-
assert_not_predicate @connection, :active?
728+
notifications.each do |n|
729+
assert_not n.payload[:allow_retry]
730+
end
757731
end
758732

759-
test "queries containing SQL functions are not retried" do
760-
Post.first
761-
762-
remote_disconnect @connection
763-
733+
test "queries containing SQL functions do not allow retries" do
764734
tags_count_attr = Post.arel_table[:tags_count]
765735
abs_tags_count = Arel::Nodes::NamedFunction.new("ABS", [tags_count_attr])
766736

767-
assert_raises(ActiveRecord::ConnectionFailed) do
737+
notifications = capture_notifications("sql.active_record") do
768738
Post.where(abs_tags_count.eq(2)).first
739+
end.select { |n| n.payload[:name] != "SCHEMA" }
740+
741+
assert_equal 1, notifications.length
742+
743+
notifications.each do |n|
744+
assert_not n.payload[:allow_retry]
769745
end
770-
assert_not_predicate @connection, :active?
771746
end
772747

773748
test "transaction restores after remote disconnection" do

guides/source/active_support_instrumentation.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ The `:cache_hits` key is only included if the collection is rendered with `cache
362362
| `:binds` | Bind parameters |
363363
| `:type_casted_binds` | Typecasted bind parameters |
364364
| `:async` | `true` if query is loaded asynchronously |
365+
| `:allow_retry` | `true` if the query can be automatically retried |
365366
| `:connection` | Connection object |
366367
| `:transaction` | Current transaction, if any |
367368
| `:affected_rows` | Number of rows affected by the query |
@@ -378,6 +379,7 @@ Adapters may add their own data as well.
378379
binds: [<ActiveModel::Attribute::WithCastValue:0x00007fe19d15dc00>],
379380
type_casted_binds: [11],
380381
async: false,
382+
allow_retry: true,
381383
connection: <ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f9f7a838850>,
382384
transaction: <ActiveRecord::ConnectionAdapters::RealTransaction:0x0000000121b5d3e0>
383385
affected_rows: 0

0 commit comments

Comments
 (0)