Skip to content

Commit 683bb07

Browse files
committed
Better add_lock! method that can add the lock to just about all the elements in the statement. This could be eager loaded associations, joins, etc. Done so that paginated results can easily add lock options for performance. Note, the tally count in add_limit_offset! use "WITH (NOLOCK)" explicitly as it can not hurt and is needed.
1 parent 85a7810 commit 683bb07

File tree

4 files changed

+36
-5
lines changed

4 files changed

+36
-5
lines changed

CHANGELOG

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11

22
MASTER
33

4-
*
4+
* Better add_lock! method that can add the lock to just about all the elements in the statement. This
5+
could be eager loaded associations, joins, etc. Done so that paginated results can easily add lock
6+
options for performance. Note, the tally count in add_limit_offset! use "WITH (NOLOCK)" explicitly
7+
as it can not hurt and is needed. [Ken Collins]
58

69

710
* 2.2.14 * (March 17th, 2009)

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,9 @@ def add_limit_offset!(sql, options)
394394
end
395395
# The business of adding limit/offset
396396
if options[:limit] and options[:offset]
397-
total_rows = select_value("SELECT count(*) as TotalRows from (#{sql.sub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP 1000000000")}) tally").to_i
397+
tally_sql = "SELECT count(*) as TotalRows from (#{sql.sub(/\bSELECT(\s+DISTINCT)?\b/i, "SELECT#{$1} TOP 1000000000")}) tally"
398+
add_lock! tally_sql, :lock => 'WITH (NOLOCK)'
399+
total_rows = select_value(tally_sql).to_i
398400
if (options[:limit] + options[:offset]) >= total_rows
399401
options[:limit] = (total_rows - options[:offset] >= 0) ? (total_rows - options[:offset]) : 0
400402
end
@@ -439,8 +441,8 @@ def add_lock!(sql, options)
439441
# http://blog.sqlauthority.com/2007/04/27/sql-server-2005-locking-hints-and-examples/
440442
return unless options[:lock]
441443
lock_type = options[:lock] == true ? 'WITH(HOLDLOCK, ROWLOCK)' : options[:lock]
442-
from_table = sql.match(/FROM(.*)WHERE/im)[1]
443-
sql.sub! from_table, "#{from_table}#{lock_type} "
444+
sql.gsub! %r|LEFT OUTER JOIN\s+(.*?)\s+ON|im, "LEFT OUTER JOIN \\1 #{lock_type} ON"
445+
sql.gsub! %r{FROM\s([\w\[\]\.]+)}im, "FROM \\1 #{lock_type}"
444446
end
445447

446448
def empty_insert_statement(table_name)

test/cases/pessimistic_locking_test_sqlserver.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,34 @@ def setup
4949
end
5050
end
5151
end
52+
53+
should 'simply add lock to find all' do
54+
assert_sql %r|SELECT \* FROM \[people\] WITH \(NOLOCK\)| do
55+
Person.all(:lock => 'WITH (NOLOCK)')
56+
end
57+
end
58+
59+
end
60+
61+
context 'For paginated finds' do
62+
63+
setup do
64+
20.times { |n| Person.create!(:first_name => "Thing_#{n}") }
65+
end
66+
67+
should 'cope with un-locked paginated results' do
68+
tally_not_locked = %r|SELECT count\(\*\) as TotalRows from \(SELECT TOP 1000000000 \* FROM \[people\]\s+WITH \(NOLOCK\) \) tally|
69+
inner_tmp_not_locked = %r|SELECT TOP 15 \* FROM \[people\] WITH \(NOLOCK\)|
70+
# Currently association limiting is not locked like the parent.
71+
association_limiting_not_locked = %r|SELECT \[readers\]\.\* FROM \[readers\] WITH \(NOLOCK\) WHERE \(\[readers\]\.person_id IN \(1,2,3,4,5\)\)|
72+
assert_sql(tally_not_locked,inner_tmp_not_locked) do
73+
Person.all(:include => :readers, :lock => 'WITH (NOLOCK)', :limit => 5, :offset => 10)
74+
end
75+
end
5276

5377
end
5478

79+
5580
context 'For dueling concurrent connections' do
5681

5782
use_concurrent_connections

test/cases/sqlserver_helper.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ def method_added(method)
7878
# Our changes/additions to ActiveRecord test helpers specific for SQL Server.
7979

8080
ActiveRecord::Base.connection.class.class_eval do
81-
IGNORED_SQL << /SELECT SCOPE_IDENTITY/ << /INFORMATION_SCHEMA.TABLES/ << /INFORMATION_SCHEMA.COLUMNS/ << /^SELECT @@TRANCOUNT/
81+
IGNORED_SQL << %r|SELECT SCOPE_IDENTITY| << %r{INFORMATION_SCHEMA\.(TABLES|VIEWS|COLUMNS)}
82+
IGNORED_SQL << %r|SELECT @@IDENTITY| << %r|SELECT @@ROWCOUNT| << %r|SELECT @@version| << %r|SELECT @@TRANCOUNT|
8283
end
8384

8485
ActiveRecord::ConnectionAdapters::SQLServerAdapter.class_eval do

0 commit comments

Comments
 (0)