Skip to content

Commit 929c60f

Browse files
byrootjoelhawksley
authored andcommitted
Refactor SQLite3Adapter to simplify transaction management
Followup: rails#52428 This adapter was overly complex following rails#37798. I'm also not convinced storing the `read_uncommited` value in the execution state was really valid, it's a connection property. We can assume outside of transaction, `read_uncommited` is set to the default, and simply reset it at the end of any transaction that did change it. This makes SQLite3Adapter much less of a snowflake.
1 parent eabb9ee commit 929c60f

File tree

5 files changed

+32
-25
lines changed

5 files changed

+32
-25
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,15 @@ def begin_isolated_db_transaction(isolation)
435435
raise ActiveRecord::TransactionIsolationError, "adapter does not support setting transaction isolation"
436436
end
437437

438+
# Hook point called after an isolated DB transaction is committed
439+
# or rolled back.
440+
# Most adapters don't need to implement anything because the isolation
441+
# level is set on a per transaction basis.
442+
# But some databases like SQLite set it on a per connection level
443+
# and need to explicitly reset it after commit or rollback.
444+
def reset_isolation_level
445+
end
446+
438447
# Commits the transaction (and turns on auto-committing).
439448
def commit_db_transaction() end
440449

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,13 +476,19 @@ def restart
476476
end
477477

478478
def rollback
479-
connection.rollback_db_transaction if materialized?
479+
if materialized?
480+
connection.rollback_db_transaction
481+
connection.reset_isolation_level if isolation_level
482+
end
480483
@state.full_rollback!
481484
@instrumenter.finish(:rollback) if materialized?
482485
end
483486

484487
def commit
485-
connection.commit_db_transaction if materialized?
488+
if materialized?
489+
connection.commit_db_transaction
490+
connection.reset_isolation_level if isolation_level
491+
end
486492
@state.full_commit!
487493
@instrumenter.finish(:commit) if materialized?
488494
end

activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ def begin_db_transaction # :nodoc:
225225
end
226226

227227
def begin_isolated_db_transaction(isolation) # :nodoc:
228+
# From MySQL manual: The [SET TRANSACTION] statement applies only to the next single transaction performed within the session.
229+
# So we don't need to implement #reset_isolation_level
228230
internal_execute("SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
229231
begin_db_transaction
230232
end

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

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,11 @@ def begin_db_transaction # :nodoc:
3434
end
3535

3636
def commit_db_transaction # :nodoc:
37-
log("commit transaction", "TRANSACTION") do
38-
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
39-
conn.commit
40-
end
41-
end
42-
reset_read_uncommitted
37+
internal_execute("COMMIT TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false)
4338
end
4439

4540
def exec_rollback_db_transaction # :nodoc:
46-
log("rollback transaction", "TRANSACTION") do
47-
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
48-
conn.rollback
49-
end
50-
end
51-
reset_read_uncommitted
41+
internal_execute("ROLLBACK TRANSACTION", "TRANSACTION", allow_retry: true, materialize_transactions: false)
5242
end
5343

5444
# https://stackoverflow.com/questions/17574784
@@ -66,23 +56,22 @@ def execute(...) # :nodoc:
6656
super&.to_a
6757
end
6858

59+
def reset_isolation_level # :nodoc:
60+
internal_execute("PRAGMA read_uncommitted=#{@previous_read_uncommitted}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
61+
@previous_read_uncommitted = nil
62+
end
63+
6964
private
7065
def internal_begin_transaction(mode, isolation)
7166
if isolation
7267
raise TransactionIsolationError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
7368
raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
7469
end
7570

76-
log("begin #{mode} transaction", "TRANSACTION") do
77-
with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
78-
if isolation
79-
ActiveSupport::IsolatedExecutionState[:active_record_read_uncommitted] = conn.get_first_value("PRAGMA read_uncommitted")
80-
conn.read_uncommitted = true
81-
end
82-
result = conn.transaction(mode)
83-
verified!
84-
result
85-
end
71+
internal_execute("BEGIN #{mode} TRANSACTION", allow_retry: true, materialize_transactions: false)
72+
if isolation
73+
@previous_read_uncommitted = query_value("PRAGMA read_uncommitted")
74+
internal_execute("PRAGMA read_uncommitted=ON", "TRANSACTION", allow_retry: true, materialize_transactions: false)
8675
end
8776
end
8877

@@ -142,7 +131,7 @@ def reset_read_uncommitted
142131
def execute_batch(statements, name = nil)
143132
sql = combine_multi_statements(statements)
144133

145-
log(sql, name) do |notification_payload|
134+
log(sql, name) do
146135
with_raw_connection do |conn|
147136
conn.execute_batch2(sql)
148137
verified!

activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ def initialize(...)
120120
end
121121

122122
@last_affected_rows = nil
123+
@previous_read_uncommitted = nil
123124
@config[:strict] = ConnectionAdapters::SQLite3Adapter.strict_strings_by_default unless @config.key?(:strict)
124125
@connection_parameters = @config.merge(
125126
database: @config[:database].to_s,

0 commit comments

Comments
 (0)