Skip to content

Commit 2990ce3

Browse files
authored
Merge pull request rails#54436 from skipkayhil/hm-retryable-statement-cached-queries
Enable statement-cached queries to be retryable
2 parents fc01241 + 4b8288e commit 2990ce3

File tree

5 files changed

+27
-14
lines changed

5 files changed

+27
-14
lines changed

activerecord/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
* Enable automatically retrying idempotent association queries on connection
2+
errors.
3+
4+
*Hartley McGuire*
5+
16
* Add `allow_retry` to `sql.active_record` instrumentation.
27

38
This enables identifying queries which are and are not automatically

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,15 @@ def to_sql_and_binds(arel_or_sql_string, binds = [], preparable = nil, allow_ret
5555
# can be used to query the database repeatedly.
5656
def cacheable_query(klass, arel) # :nodoc:
5757
if prepared_statements
58+
collector = collector()
59+
collector.retryable = true
5860
sql, binds = visitor.compile(arel.ast, collector)
59-
query = klass.query(sql)
61+
query = klass.query(sql, retryable: collector.retryable)
6062
else
6163
collector = klass.partial_query_collector
64+
collector.retryable = true
6265
parts, binds = visitor.compile(arel.ast, collector)
63-
query = klass.partial_query(parts)
66+
query = klass.partial_query(parts, retryable: collector.retryable)
6467
end
6568
[query, binds]
6669
end

activerecord/lib/active_record/core.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ def cached_find_by(keys, values)
450450
where(wheres).limit(1)
451451
}
452452

453-
statement.execute(values.flatten, connection, allow_retry: true).then do |r|
453+
statement.execute(values.flatten, connection).then do |r|
454454
r.first
455455
rescue TypeError
456456
raise ActiveRecord::StatementInvalid

activerecord/lib/active_record/statement_cache.rb

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@ class StatementCache # :nodoc:
3131
class Substitute; end # :nodoc:
3232

3333
class Query # :nodoc:
34-
def initialize(sql)
34+
attr_reader :retryable
35+
36+
def initialize(sql, retryable:)
3537
@sql = sql
38+
@retryable = retryable
3639
end
3740

3841
def sql_for(binds, connection)
@@ -41,11 +44,12 @@ def sql_for(binds, connection)
4144
end
4245

4346
class PartialQuery < Query # :nodoc:
44-
def initialize(values)
47+
def initialize(values, retryable:)
4548
@values = values
4649
@indexes = values.each_with_index.find_all { |thing, i|
4750
Substitute === thing
4851
}.map(&:last)
52+
@retryable = retryable
4953
end
5054

5155
def sql_for(binds, connection)
@@ -94,12 +98,12 @@ def value
9498
end
9599
end
96100

97-
def self.query(sql)
98-
Query.new(sql)
101+
def self.query(...)
102+
Query.new(...)
99103
end
100104

101-
def self.partial_query(values)
102-
PartialQuery.new(values)
105+
def self.partial_query(...)
106+
PartialQuery.new(...)
103107
end
104108

105109
def self.partial_query_collector
@@ -142,14 +146,14 @@ def initialize(query_builder, bind_map, model)
142146
@model = model
143147
end
144148

145-
def execute(params, connection, allow_retry: false, async: false, &block)
149+
def execute(params, connection, async: false, &block)
146150
bind_values = @bind_map.bind params
147151
sql = @query_builder.sql_for bind_values, connection
148152

149153
if async
150-
@model.async_find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
154+
@model.async_find_by_sql(sql, bind_values, preparable: true, allow_retry: @query_builder.retryable, &block)
151155
else
152-
@model.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
156+
@model.find_by_sql(sql, bind_values, preparable: true, allow_retry: @query_builder.retryable, &block)
153157
end
154158
rescue ::RangeError
155159
async ? Promise.wrap([]) : []

activerecord/test/cases/adapter_test.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -702,14 +702,15 @@ def teardown
702702

703703
test "idempotent SELECT queries allow retries" do
704704
notifications = capture_notifications("sql.active_record") do
705-
assert Post.first
705+
assert (a = Author.first)
706706
assert Post.where(id: [1, 2]).first
707707
assert Post.find(1)
708708
assert Post.find_by(title: "Welcome to the weblog")
709709
assert_predicate Post, :exists?
710+
a.books.to_a
710711
end.select { |n| n.payload[:name] != "SCHEMA" }
711712

712-
assert_equal 5, notifications.length
713+
assert_equal 6, notifications.length
713714

714715
notifications.each do |n|
715716
assert n.payload[:allow_retry]

0 commit comments

Comments
 (0)