Skip to content

Commit

Permalink
Supply run_with_isolation_level method to allow running queries at va…
Browse files Browse the repository at this point in the history
…rious isolation levels (e.g. to go around other processes locking tables).
  • Loading branch information
h-lame authored and metaskills committed Apr 20, 2009
1 parent b91ec6f commit f165a9d
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 0 deletions.
31 changes: 31 additions & 0 deletions lib/active_record/connection_adapters/sqlserver_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,37 @@ def finish_statement_handle(handle)

# DATABASE STATEMENTS ======================================#

# Returns the SET options active for the current connection.
def user_options
values = {}
select_rows("dbcc useroptions").each {|field| values.merge!(field[0].to_sym => field[1])}
values
end

VALID_ISOLATION_LEVELS = ["READ UNCOMMITTED", "READ COMMITTED", "REPEATABLE READ", "SNAPSHOT", "SERIALIZABLE"]

# Runs a block with a given isolation level.
# Supported isolation levels include
# * <tt>"READ UNCOMMITTED"</tt>
# * <tt>"READ COMMITTED"</tt>
# * <tt>"REPEATABLE READ"</tt>
# * <tt>"SERIALIZABLE"</tt>
# * <tt>"SNAPSHOT"</tt>
def run_with_isolation_level(isolation_level, &block)
if !VALID_ISOLATION_LEVELS.include?(isolation_level.upcase)
raise ArgumentError, "#{isolation_level} not a supported isolation level. Supported isolation levels are #{VALID_ISOLATION_LEVELS.to_sentence}"
end

initial_isolation_level = user_options[:"isolation level"] || "READ COMMITTED"
execute "SET TRANSACTION ISOLATION LEVEL #{isolation_level}"
begin
result = yield
return result
ensure
execute "SET TRANSACTION ISOLATION LEVEL #{initial_isolation_level}"
end
end

def select_rows(sql, name = nil)
raw_select(sql,name).last
end
Expand Down
62 changes: 62 additions & 0 deletions test/cases/adapter_test_sqlserver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,68 @@ def setup

context 'For DatabaseStatements' do

context "finding out what user_options are available" do
should "run the database consistency checker useroptions command" do
@connection.expects(:select_rows).with(regexp_matches(/^dbcc\s+useroptions$/i)).returns []
@connection.user_options
end

should "return a symbolized hash of the results" do
@connection.expects(:select_rows).with(regexp_matches(/^dbcc\s+useroptions$/i)).returns [['some', 'thing'], ['an', 'other thing']]
res = @connection.user_options
assert_equal 'thing', res[:some]
assert_equal 'other thing', res[:an]
assert_equal 2, res.keys.size
end
end

context "altering isolation levels" do
should "barf if the requested isolation level is not valid" do
@connection.class::VALID_ISOLATION_LEVELS.expects(:include?).returns false
assert_raise(ArgumentError) do
@connection.run_with_isolation_level 'something' do; end
end
end

context "with a valid isolation level" do
setup do
@connection.class::VALID_ISOLATION_LEVELS.expects(:include?).returns true
@connection.stubs(:user_options).returns({:"isolation level" => "something"})
@yieldy = states('yield').starts_as(:not_yielded)
end

should "set the isolation level to that supplied before calling the supplied block" do
@connection.expects(:execute).with(regexp_matches(/set transaction isolation level new_isolation_level/i)).when(@yieldy.is(:not_yielded))
@connection.stubs(:execute).when(@yieldy.is(:yielded))

@connection.run_with_isolation_level 'new_isolation_level' do
@yieldy.become(:yielded)
end
end

should "set the isolation level back to the original after calling the supplied block" do
@connection.expects(:execute).with(regexp_matches(/set transaction isolation level something/i)).when(@yieldy.is(:yielded))
@connection.stubs(:execute).when(@yieldy.is(:not_yielded))

@connection.run_with_isolation_level 'new_isolation_level' do
@yieldy.become(:yielded)
end
end

should "set the isolation level back to the original after calling the supplied block even when the block raises an exception" do
@connection.expects(:execute).with(regexp_matches(/set transaction isolation level something/i)).when(@yieldy.is(:yielded))
@connection.stubs(:execute).when(@yieldy.is(:not_yielded))

assert_raise(RuntimeError) do
@connection.run_with_isolation_level 'new_isolation_level' do
@yieldy.become(:yielded)
raise "a problem"
end
end
end
end
end

end

context 'For SchemaStatements' do
Expand Down

0 comments on commit f165a9d

Please sign in to comment.