未验证 提交 b4ab1f19 编写于 作者: R Rafael França 提交者: GitHub

Merge pull request #37798 from Edouard-chin/ec-sqlite3-connection-transaction

Added posibility to open a `read_uncommitted` transaction on SQLite:
......@@ -74,19 +74,37 @@ def exec_delete(sql, name = "SQL", binds = [])
end
alias :exec_update :exec_delete
def begin_isolated_db_transaction(isolation) #:nodoc
raise ArgumentError, "SQLite3 only supports the `read_uncommitted` transaction isolation level" if isolation != :read_uncommitted
raise StandardError, "You need to enable the shared-cache mode in SQLite mode before attempting to change the transaction isolation level" unless shared_cache?
Thread.current.thread_variable_set("read_uncommitted", @connection.get_first_value("PRAGMA read_uncommitted"))
@connection.read_uncommitted = true
begin_db_transaction
end
def begin_db_transaction #:nodoc:
log("begin transaction", "TRANSACTION") { @connection.transaction }
end
def commit_db_transaction #:nodoc:
log("commit transaction", "TRANSACTION") { @connection.commit }
reset_read_uncommitted
end
def exec_rollback_db_transaction #:nodoc:
log("rollback transaction", "TRANSACTION") { @connection.rollback }
reset_read_uncommitted
end
private
def reset_read_uncommitted
read_uncommitted = Thread.current.thread_variable_get("read_uncommitted")
return unless read_uncommitted
@connection.read_uncommitted = read_uncommitted
end
def execute_batch(statements, name = nil)
sql = combine_multi_statements(statements)
......
......@@ -26,7 +26,7 @@ def sqlite3_connection(config)
# Allow database path relative to Rails.root, but only if the database
# path is not the special path that tells sqlite to build a database only
# in memory.
if ":memory:" != config[:database]
if ":memory:" != config[:database] && !config[:database].to_s.starts_with?("file:")
config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
dirname = File.dirname(config[:database])
Dir.mkdir(dirname) unless File.directory?(dirname)
......@@ -116,6 +116,10 @@ def supports_savepoints?
true
end
def supports_transaction_isolation?
true
end
def supports_partial_index?
true
end
......@@ -325,6 +329,10 @@ def build_insert_sql(insert) # :nodoc:
sql
end
def shared_cache? # :nodoc:
@config.fetch(:flags, 0).anybits?(::SQLite3::Constants::Open::SHAREDCACHE)
end
def get_database_version # :nodoc:
SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)"))
end
......
# frozen_string_literal: true
require "cases/helper"
class SQLite3TransactionTest < ActiveRecord::SQLite3TestCase
test "shared_cached? is true when cache-mode is enabled" do
with_connection(flags: shared_cache_flags) do |conn|
assert_predicate(conn, :shared_cache?)
end
end
test "shared_cached? is false when cache-mode is disabled" do
flags =::SQLite3::Constants::Open::READWRITE | SQLite3::Constants::Open::CREATE
with_connection(flags: flags) do |conn|
assert_not_predicate(conn, :shared_cache?)
end
end
test "raises when trying to open a transaction in a isolation level other than `read_uncommitted`" do
with_connection do |conn|
assert_raises(ArgumentError) do
conn.transaction(requires_new: true, isolation: :something) do
conn.transaction_manager.materialize_transactions
end
end
end
end
test "raises when trying to open a read_uncommitted transaction but shared-cache mode is turned off" do
with_connection do |conn|
error = assert_raises(StandardError) do
conn.transaction(requires_new: true, isolation: :read_uncommitted) do
conn.transaction_manager.materialize_transactions
end
end
assert_match("You need to enable the shared-cache mode", error.message)
end
end
test "opens a `read_uncommitted` transaction" do
with_connection(flags: shared_cache_flags) do |conn1|
conn1.create_table(:zines) { |t| t.column(:title, :string) } if in_memory_db?
conn1.transaction do
conn1.transaction_manager.materialize_transactions
conn1.execute("INSERT INTO zines (title) VALUES ('foo')")
with_connection(flags: shared_cache_flags) do |conn2|
conn2.transaction(joinable: false, isolation: :read_uncommitted) do
assert_not_empty(conn2.execute("SELECT * FROM zines WHERE title = 'foo'"))
end
end
raise ActiveRecord::Rollback
end
end
end
test "reset the read_uncommitted PRAGMA when transactions is rolled back" do
with_connection(flags: shared_cache_flags) do |conn|
conn.transaction(joinable: false, isolation: :read_uncommitted) do
assert_not(read_uncommitted?(conn))
conn.transaction_manager.materialize_transactions
assert(read_uncommitted?(conn))
raise ActiveRecord::Rollback
end
assert_not(read_uncommitted?(conn))
end
end
test "reset the read_uncommitted PRAGMA when transactions is commited" do
with_connection(flags: shared_cache_flags) do |conn|
conn.transaction(joinable: false, isolation: :read_uncommitted) do
assert_not(read_uncommitted?(conn))
conn.transaction_manager.materialize_transactions
assert(read_uncommitted?(conn))
end
assert_not(read_uncommitted?(conn))
end
end
test "set the read_uncommited PRAGMA to its previous value" do
with_connection(flags: shared_cache_flags) do |conn|
conn.transaction(joinable: false, isolation: :read_uncommitted) do
conn.instance_variable_get(:@connection).read_uncommitted = true
assert(read_uncommitted?(conn))
conn.transaction_manager.materialize_transactions
assert(read_uncommitted?(conn))
end
assert(read_uncommitted?(conn))
end
end
private
def read_uncommitted?(conn)
conn.instance_variable_get(:@connection).get_first_value("PRAGMA read_uncommitted") != 0
end
def shared_cache_flags
::SQLite3::Constants::Open::READWRITE | SQLite3::Constants::Open::CREATE | ::SQLite3::Constants::Open::SHAREDCACHE | ::SQLite3::Constants::Open::URI
end
def with_connection(options = {})
conn_options = options.reverse_merge(
database: in_memory_db? ? "file::memory:" : ActiveRecord::Base.configurations["arunit"][:database]
)
conn = ActiveRecord::Base.sqlite3_connection(conn_options)
yield(conn)
ensure
conn.disconnect! if conn
end
end
......@@ -2,7 +2,7 @@
require "cases/helper"
unless ActiveRecord::Base.connection.supports_transaction_isolation?
unless ActiveRecord::Base.connection.supports_transaction_isolation? && !current_adapter?(:SQLite3Adapter)
class TransactionIsolationUnsupportedTest < ActiveRecord::TestCase
self.use_transactional_tests = false
......@@ -10,6 +10,8 @@ class Tag < ActiveRecord::Base
end
test "setting the isolation level raises an error" do
skip if current_adapter?(:SQLite3Adapter)
assert_raises(ActiveRecord::TransactionIsolationError) do
Tag.transaction(isolation: :serializable) { Tag.connection.materialize_transactions }
end
......
......@@ -1127,7 +1127,7 @@ def test_no_automatic_savepoint_for_inner_transaction
end
end if Topic.connection.supports_savepoints?
if ActiveRecord::Base.connection.supports_transaction_isolation?
if ActiveRecord::Base.connection.supports_transaction_isolation? && !in_memory_db?
class ConcurrentTransactionTest < TransactionTest
# This will cause transactions to overlap and fail unless they are performed on
# separate database connections.
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册