activerecord-jdbc-adapter 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +3 -0
- data/Gemfile.lock +13 -15
- data/History.txt +19 -0
- data/README.rdoc +2 -0
- data/Rakefile +2 -1
- data/lib/arel/visitors/derby.rb +9 -2
- data/lib/arel/visitors/sql_server.rb +2 -0
- data/lib/arjdbc/db2/adapter.rb +3 -1
- data/lib/arjdbc/derby/adapter.rb +10 -3
- data/lib/arjdbc/jdbc/adapter.rb +5 -2
- data/lib/arjdbc/jdbc/adapter_java.jar +0 -0
- data/lib/arjdbc/jdbc/base_ext.rb +15 -0
- data/lib/arjdbc/jdbc/connection.rb +5 -1
- data/lib/arjdbc/jdbc/missing_functionality_helper.rb +1 -0
- data/lib/arjdbc/mssql/adapter.rb +31 -28
- data/lib/arjdbc/mssql/lock_helpers.rb +72 -0
- data/lib/arjdbc/mysql/adapter.rb +110 -45
- data/lib/arjdbc/oracle/adapter.rb +7 -0
- data/lib/arjdbc/postgresql/adapter.rb +327 -153
- data/lib/arjdbc/sqlite3/adapter.rb +9 -4
- data/lib/arjdbc/version.rb +1 -1
- data/rakelib/db.rake +17 -5
- data/src/java/arjdbc/jdbc/RubyJdbcConnection.java +14 -4
- data/src/java/arjdbc/postgresql/PostgresqlRubyJdbcConnection.java +25 -0
- data/test/db/jdbc.rb +4 -3
- data/test/db2_reset_column_information_test.rb +8 -0
- data/test/derby_reset_column_information_test.rb +8 -0
- data/test/derby_row_locking_test.rb +9 -0
- data/test/derby_simple_test.rb +40 -0
- data/test/h2_simple_test.rb +2 -2
- data/test/helper.rb +15 -2
- data/test/jdbc_common.rb +1 -0
- data/test/jndi_callbacks_test.rb +5 -9
- data/test/manualTestDatabase.rb +31 -31
- data/test/models/validates_uniqueness_of_string.rb +1 -1
- data/test/mssql_ignore_system_views_test.rb +27 -0
- data/test/mssql_null_test.rb +14 -0
- data/test/mssql_reset_column_information_test.rb +8 -0
- data/test/mssql_row_locking_sql_test.rb +159 -0
- data/test/mssql_row_locking_test.rb +9 -0
- data/test/mysql_reset_column_information_test.rb +8 -0
- data/test/mysql_simple_test.rb +69 -5
- data/test/oracle_reset_column_information_test.rb +8 -0
- data/test/oracle_specific_test.rb +1 -1
- data/test/postgres_nonseq_pkey_test.rb +1 -1
- data/test/postgres_reset_column_information_test.rb +8 -0
- data/test/postgres_simple_test.rb +72 -1
- data/test/row_locking.rb +90 -0
- data/test/simple.rb +82 -2
- data/test/sqlite3_reset_column_information_test.rb +8 -0
- data/test/sqlite3_simple_test.rb +47 -0
- data/test/sybase_reset_column_information_test.rb +8 -0
- metadata +33 -3
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,35 +1,33 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
-
activemodel (3.1
|
5
|
-
activesupport (= 3.1
|
6
|
-
bcrypt-ruby (~> 3.0.0)
|
4
|
+
activemodel (3.2.1)
|
5
|
+
activesupport (= 3.2.1)
|
7
6
|
builder (~> 3.0.0)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
arel (~> 2.2.1)
|
7
|
+
activerecord (3.2.1)
|
8
|
+
activemodel (= 3.2.1)
|
9
|
+
activesupport (= 3.2.1)
|
10
|
+
arel (~> 3.0.0)
|
13
11
|
tzinfo (~> 0.3.29)
|
14
|
-
activesupport (3.1
|
12
|
+
activesupport (3.2.1)
|
13
|
+
i18n (~> 0.6)
|
15
14
|
multi_json (~> 1.0)
|
16
|
-
arel (
|
17
|
-
bcrypt-ruby (3.0.0-java)
|
15
|
+
arel (3.0.0)
|
18
16
|
bouncy-castle-java (1.5.0146.1)
|
19
17
|
builder (3.0.0)
|
20
18
|
columnize (0.3.1)
|
21
19
|
i18n (0.6.0)
|
22
|
-
jruby-openssl (0.7.
|
23
|
-
bouncy-castle-java
|
20
|
+
jruby-openssl (0.7.5)
|
21
|
+
bouncy-castle-java (>= 1.5.0146.1)
|
24
22
|
mocha (0.9.8)
|
25
23
|
rake
|
26
|
-
multi_json (1.0.
|
24
|
+
multi_json (1.0.4)
|
27
25
|
rake (0.9.2.2)
|
28
26
|
ruby-debug (0.10.4)
|
29
27
|
columnize (>= 0.1)
|
30
28
|
ruby-debug-base (~> 0.10.4.0)
|
31
29
|
ruby-debug-base (0.10.4-java)
|
32
|
-
tzinfo (0.3.
|
30
|
+
tzinfo (0.3.31)
|
33
31
|
|
34
32
|
PLATFORMS
|
35
33
|
java
|
data/History.txt
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
== 1.2.2 (01/27/12)
|
2
|
+
|
3
|
+
- Thanks George Murphy and Dwayne Litzenberger for their significant
|
4
|
+
work this release!
|
5
|
+
- AR 3.2.x compatibility via #156 (thanks George Murphy)
|
6
|
+
- #152: Bunch of derby and mssql fixes (thanks Dwayne Litzenberger)
|
7
|
+
- #137: Fix configure_arel2_visitors for vanilla JDBC adapters
|
8
|
+
- #136: query cache fix
|
9
|
+
- #138: error message improvement for #table_structure (threez)
|
10
|
+
- #130, #139: sqlite3 should log inserts (Uwe Kubosch)
|
11
|
+
- #141 column queries logging (George Murphy)
|
12
|
+
- #142 MySQL fixes for AR 3-1-stable tests (George Murphy)
|
13
|
+
- #147, #149 Improve speed of PG metadata queries (George Murphy)
|
14
|
+
- #148 PostgreSQL fixes for AR 3-1-stable tests (George Murphy)
|
15
|
+
- #128, #129 Fix for invalid :limit on date columns in schema.rb (Lenny Marks)
|
16
|
+
- #144 Stop using ParseDate (not 1.9 friendly) (Bill Koch)
|
17
|
+
- #146 Upgrade PG drivers (David Kellum)
|
18
|
+
- #150 avoid 'TypeError: can't dup Fixnum' for performance (Bruce Adams)
|
19
|
+
|
1
20
|
== 1.2.1 (11/23/11)
|
2
21
|
|
3
22
|
- #117: Skip ? substitution when no bind parameters are given
|
data/README.rdoc
CHANGED
@@ -182,6 +182,8 @@ the ActiveRecord sources.
|
|
182
182
|
|
183
183
|
jruby -S rake rails:test DRIVER=mysql RAILS=/path/activerecord_source_dir
|
184
184
|
|
185
|
+
== Travis Build Status {<img src="https://secure.travis-ci.org/jruby/activerecord-jdbc-adapter.png"/>}[http://travis-ci.org/#!/jruby/activerecord-jdbc-adapter]
|
186
|
+
|
185
187
|
== Authors
|
186
188
|
|
187
189
|
This project was written by Nick Sieger <nick@nicksieger.com> and Ola Bini
|
data/Rakefile
CHANGED
@@ -6,6 +6,8 @@ require 'bundler'
|
|
6
6
|
Bundler::GemHelper.install_tasks
|
7
7
|
require 'bundler/setup'
|
8
8
|
|
9
|
+
require File.expand_path('../test/helper', __FILE__)
|
10
|
+
|
9
11
|
task :default => [:jar, :test]
|
10
12
|
|
11
13
|
#ugh, bundler doesn't use tasks, so gotta hook up to both tasks.
|
@@ -56,4 +58,3 @@ task "all:build" => ["build", *ADAPTERS.map { |f| "#{f}:build" }]
|
|
56
58
|
task :filelist do
|
57
59
|
puts FileList['pkg/**/*'].inspect
|
58
60
|
end
|
59
|
-
|
data/lib/arel/visitors/derby.rb
CHANGED
@@ -5,10 +5,10 @@ module Arel
|
|
5
5
|
class Derby < Arel::Visitors::ToSql
|
6
6
|
def visit_Arel_Nodes_SelectStatement o
|
7
7
|
[
|
8
|
-
o.cores.map { |x|
|
8
|
+
o.cores.map { |x| visit(x) }.join,
|
9
9
|
("ORDER BY #{o.orders.map { |x| visit x }.join(', ')}" unless o.orders.empty?),
|
10
|
-
(visit_Arel_Nodes_Limit(o.limit) if o.limit),
|
11
10
|
(visit(o.offset) if o.offset),
|
11
|
+
(visit(o.limit) if o.limit),
|
12
12
|
(visit(o.lock) if o.lock),
|
13
13
|
].compact.join ' '
|
14
14
|
end
|
@@ -20,6 +20,13 @@ module Arel
|
|
20
20
|
def visit_Arel_Nodes_Offset o
|
21
21
|
"OFFSET #{visit o.value} ROWS"
|
22
22
|
end
|
23
|
+
|
24
|
+
# This generates SELECT...FOR UPDATE, but it will only work if the
|
25
|
+
# current transaction isolation level is set to SERIALIZABLE. Otherwise,
|
26
|
+
# locks aren't held for the entire transaction.
|
27
|
+
def visit_Arel_Nodes_Lock o
|
28
|
+
visit o.expr
|
29
|
+
end
|
23
30
|
end
|
24
31
|
end
|
25
32
|
end
|
@@ -4,6 +4,7 @@ module Arel
|
|
4
4
|
module Visitors
|
5
5
|
class SQLServer < Arel::Visitors::ToSql
|
6
6
|
include ArJdbc::MsSQL::LimitHelpers::SqlServerReplaceLimitOffset
|
7
|
+
include ArJdbc::MsSQL::LockHelpers::SqlServerAddLock
|
7
8
|
|
8
9
|
def select_count? o
|
9
10
|
sel = o.cores.length == 1 && o.cores.first
|
@@ -33,6 +34,7 @@ module Arel
|
|
33
34
|
else
|
34
35
|
sql = super
|
35
36
|
end
|
37
|
+
add_lock!(sql, :lock => o.lock && true)
|
36
38
|
sql
|
37
39
|
end
|
38
40
|
end
|
data/lib/arjdbc/db2/adapter.rb
CHANGED
@@ -69,7 +69,9 @@ module ArJdbc
|
|
69
69
|
def self.cast_to_time(value)
|
70
70
|
return value if value.is_a? Time
|
71
71
|
# AS400 returns a 2 digit year, LUW returns a 4 digit year, so comp = true to help out AS400
|
72
|
-
|
72
|
+
time = DateTime.parse(value).to_time rescue nil
|
73
|
+
return nil unless time
|
74
|
+
time_array = [time.year, time.month, time.day, time.hour, time.min, time.sec]
|
73
75
|
time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1;
|
74
76
|
Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
|
75
77
|
end
|
data/lib/arjdbc/derby/adapter.rb
CHANGED
@@ -27,19 +27,25 @@ module ::ArJdbc
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def self.extended(
|
30
|
+
def self.extended(adapter)
|
31
31
|
monkey_rails
|
32
|
+
adapter.configure_connection
|
32
33
|
end
|
33
34
|
|
34
35
|
def self.included(*args)
|
35
36
|
monkey_rails
|
36
37
|
end
|
37
38
|
|
39
|
+
def configure_connection
|
40
|
+
execute("SET ISOLATION = SERIALIZABLE") # This must be done or SELECT...FOR UPDATE won't work how we expect
|
41
|
+
end
|
42
|
+
|
38
43
|
module Column
|
39
44
|
def simplified_type(field_type)
|
40
45
|
case field_type
|
41
46
|
when /smallint/i then :boolean
|
42
47
|
when /real/i then :float
|
48
|
+
when /^timestamp/i then :datetime
|
43
49
|
else
|
44
50
|
super
|
45
51
|
end
|
@@ -73,10 +79,10 @@ module ::ArJdbc
|
|
73
79
|
# In Derby, the following cannot specify a limit:
|
74
80
|
# - integer
|
75
81
|
# - boolean (smallint)
|
76
|
-
# - timestamp
|
82
|
+
# - datetime (timestamp)
|
77
83
|
# - date
|
78
84
|
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
|
79
|
-
return super unless [:integer, :boolean, :timestamp, :date].include? type
|
85
|
+
return super unless [:integer, :boolean, :timestamp, :datetime, :date].include? type
|
80
86
|
|
81
87
|
native = native_database_types[type.to_s.downcase.to_sym]
|
82
88
|
native.is_a?(Hash) ? native[:name] : native
|
@@ -87,6 +93,7 @@ module ::ArJdbc
|
|
87
93
|
tp[:string][:limit] = 256
|
88
94
|
tp[:integer][:limit] = nil
|
89
95
|
tp[:boolean] = {:name => "smallint"}
|
96
|
+
tp[:datetime] = {:name => "timestamp", :limit => nil}
|
90
97
|
tp[:timestamp][:limit] = nil
|
91
98
|
tp[:date][:limit] = nil
|
92
99
|
tp
|
data/lib/arjdbc/jdbc/adapter.rb
CHANGED
@@ -12,6 +12,7 @@ require 'arjdbc/jdbc/column'
|
|
12
12
|
require 'arjdbc/jdbc/connection'
|
13
13
|
require 'arjdbc/jdbc/callbacks'
|
14
14
|
require 'arjdbc/jdbc/extension'
|
15
|
+
require 'arjdbc/jdbc/base_ext'
|
15
16
|
require 'bigdecimal'
|
16
17
|
|
17
18
|
module ActiveRecord
|
@@ -111,7 +112,7 @@ module ActiveRecord
|
|
111
112
|
if defined?(::Arel::Visitors::VISITORS)
|
112
113
|
visitors = ::Arel::Visitors::VISITORS
|
113
114
|
visitor = nil
|
114
|
-
adapter_spec = config[:adapter_spec] || self
|
115
|
+
adapter_spec = config[:adapter_spec] || self.class
|
115
116
|
adapter_spec.arel2_visitors(config).each do |k,v|
|
116
117
|
visitor = v
|
117
118
|
visitors[k] = v
|
@@ -119,6 +120,7 @@ module ActiveRecord
|
|
119
120
|
if visitor && config[:adapter] =~ /^(jdbc|jndi)$/
|
120
121
|
visitors[config[:adapter]] = visitor
|
121
122
|
end
|
123
|
+
@visitor = visitors[config[:adapter]].new(self)
|
122
124
|
end
|
123
125
|
end
|
124
126
|
|
@@ -196,7 +198,8 @@ module ActiveRecord
|
|
196
198
|
if binds.empty?
|
197
199
|
sql
|
198
200
|
else
|
199
|
-
|
201
|
+
copy = binds.dup
|
202
|
+
sql.gsub('?') { quote(*copy.shift.reverse) }
|
200
203
|
end
|
201
204
|
end
|
202
205
|
|
Binary file
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Base # reopen
|
3
|
+
class <<self
|
4
|
+
# Allow adapters to provide their own reset_column_information methods
|
5
|
+
#
|
6
|
+
# NOTE: This only affects the current thread's connection.
|
7
|
+
def reset_column_information_with_arjdbc_base_ext
|
8
|
+
# Invoke the adapter-specific reset_column_information method
|
9
|
+
connection.reset_column_information if connection.respond_to?(:reset_column_information)
|
10
|
+
reset_column_information_without_arjdbc_base_ext
|
11
|
+
end
|
12
|
+
alias_method_chain :reset_column_information, :arjdbc_base_ext unless instance_methods.include?("reset_column_information_without_arjdbc_base_ext")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -107,7 +107,11 @@ module ActiveRecord
|
|
107
107
|
types = {}
|
108
108
|
@native_types.each_pair do |k, v|
|
109
109
|
types[k] = v.inject({}) do |memo, kv|
|
110
|
-
memo[kv.first] =
|
110
|
+
memo[kv.first] = if kv.last.is_a? Numeric
|
111
|
+
kv.last
|
112
|
+
else
|
113
|
+
begin kv.last.dup rescue kv.last end
|
114
|
+
end
|
111
115
|
memo
|
112
116
|
end
|
113
117
|
end
|
@@ -33,6 +33,7 @@ module ArJdbc
|
|
33
33
|
|
34
34
|
@definition.column(column_name, column.type,
|
35
35
|
:limit => column.limit, :default => column.default,
|
36
|
+
:precision => column.precision, :scale => column.scale,
|
36
37
|
:null => column.null)
|
37
38
|
end
|
38
39
|
@definition.primary_key(primary_key(from)) if primary_key(from)
|
data/lib/arjdbc/mssql/adapter.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'arjdbc/mssql/tsql_helper'
|
2
2
|
require 'arjdbc/mssql/limit_helpers'
|
3
|
+
require 'arjdbc/mssql/lock_helpers'
|
4
|
+
require 'strscan'
|
3
5
|
|
4
6
|
module ::ArJdbc
|
5
7
|
module MsSQL
|
@@ -12,7 +14,13 @@ module ::ArJdbc
|
|
12
14
|
def after_save_with_mssql_lob
|
13
15
|
self.class.columns.select { |c| c.sql_type =~ /image/i }.each do |c|
|
14
16
|
value = self[c.name]
|
15
|
-
|
17
|
+
if coder = self.class.serialized_attributes[c.name]
|
18
|
+
if coder.respond_to?(:dump)
|
19
|
+
value = coder.dump(value)
|
20
|
+
else
|
21
|
+
value = value.to_yaml
|
22
|
+
end
|
23
|
+
end
|
16
24
|
next if value.nil? || (value == '')
|
17
25
|
|
18
26
|
connection.write_large_object(c.type == :binary, c.name, self.class.table_name, self.class.primary_key, quote_value(id), value)
|
@@ -84,22 +92,26 @@ module ::ArJdbc
|
|
84
92
|
end
|
85
93
|
|
86
94
|
module Column
|
95
|
+
include LockHelpers::SqlServerAddLock
|
96
|
+
|
87
97
|
attr_accessor :identity, :is_special
|
88
98
|
|
89
99
|
def simplified_type(field_type)
|
90
100
|
case field_type
|
91
|
-
when /int|bigint|smallint|tinyint/i
|
92
|
-
when /numeric/i
|
93
|
-
when /float|double|
|
94
|
-
when /datetime|smalldatetime/i
|
95
|
-
when /timestamp/i
|
96
|
-
when /time/i
|
97
|
-
when /date/i
|
98
|
-
when /text|ntext|xml/i
|
99
|
-
when /binary|image|varbinary/i
|
100
|
-
when /char|nchar|nvarchar|string|varchar/i
|
101
|
-
when /bit/i
|
102
|
-
when /uniqueidentifier/i
|
101
|
+
when /int|bigint|smallint|tinyint/i then :integer
|
102
|
+
when /numeric/i then (@scale.nil? || @scale == 0) ? :integer : :decimal
|
103
|
+
when /float|double|money|real|smallmoney/i then :decimal
|
104
|
+
when /datetime|smalldatetime/i then :datetime
|
105
|
+
when /timestamp/i then :timestamp
|
106
|
+
when /time/i then :time
|
107
|
+
when /date/i then :date
|
108
|
+
when /text|ntext|xml/i then :text
|
109
|
+
when /binary|image|varbinary/i then :binary
|
110
|
+
when /char|nchar|nvarchar|string|varchar/i then (@limit == 1073741823 ? (@limit = nil; :text) : :string)
|
111
|
+
when /bit/i then :boolean
|
112
|
+
when /uniqueidentifier/i then :string
|
113
|
+
else
|
114
|
+
super
|
103
115
|
end
|
104
116
|
end
|
105
117
|
|
@@ -109,7 +121,7 @@ module ::ArJdbc
|
|
109
121
|
end
|
110
122
|
|
111
123
|
def type_cast(value)
|
112
|
-
return nil if value.nil?
|
124
|
+
return nil if value.nil?
|
113
125
|
case type
|
114
126
|
when :integer then value.delete('()').to_i rescue unquote(value).to_i rescue value ? 1 : 0
|
115
127
|
when :primary_key then value == true || value == false ? value == true ? 1 : 0 : value.to_i
|
@@ -143,15 +155,7 @@ module ::ArJdbc
|
|
143
155
|
|
144
156
|
def cast_to_time(value)
|
145
157
|
return value if value.is_a?(Time)
|
146
|
-
|
147
|
-
return nil if !time_array.any?
|
148
|
-
time_array[0] ||= 2000
|
149
|
-
time_array[1] ||= 1
|
150
|
-
time_array[2] ||= 1
|
151
|
-
return Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil
|
152
|
-
|
153
|
-
# Try DateTime instead - the date may be outside the time period support by Time.
|
154
|
-
DateTime.new(*time_array[0..5]) rescue nil
|
158
|
+
DateTime.parse(value).to_time rescue nil
|
155
159
|
end
|
156
160
|
|
157
161
|
def cast_to_date(value)
|
@@ -389,11 +393,6 @@ module ::ArJdbc
|
|
389
393
|
end
|
390
394
|
end
|
391
395
|
|
392
|
-
#SELECT .. FOR UPDATE is not supported on Microsoft SQL Server
|
393
|
-
def add_lock!(sql, options)
|
394
|
-
sql
|
395
|
-
end
|
396
|
-
|
397
396
|
# Turns IDENTITY_INSERT ON for table during execution of the block
|
398
397
|
# N.B. This sets the state of IDENTITY_INSERT to OFF after the
|
399
398
|
# block has been executed without regard to its previous state
|
@@ -469,6 +468,10 @@ module ::ArJdbc
|
|
469
468
|
def clear_cached_table(name)
|
470
469
|
(@table_columns ||= {}).delete(name.to_s)
|
471
470
|
end
|
471
|
+
|
472
|
+
def reset_column_information
|
473
|
+
@table_columns = nil
|
474
|
+
end
|
472
475
|
end
|
473
476
|
end
|
474
477
|
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module ::ArJdbc
|
2
|
+
module MsSQL
|
3
|
+
module LockHelpers
|
4
|
+
module SqlServerAddLock
|
5
|
+
# Microsoft SQL Server uses its own syntax for SELECT .. FOR UPDATE:
|
6
|
+
# SELECT .. FROM table1 WITH(ROWLOCK,UPDLOCK), table2 WITH(ROWLOCK,UPDLOCK) WHERE ..
|
7
|
+
#
|
8
|
+
# This does in-place modification of the passed-in string.
|
9
|
+
def add_lock!(sql, options)
|
10
|
+
if options[:lock] and sql =~ /\A\s*SELECT/mi
|
11
|
+
# Check for and extract the :limit/:offset sub-query
|
12
|
+
if sql =~ /\A(\s*SELECT t\.\* FROM \()(.*)(\) AS t WHERE t._row_num BETWEEN \d+ AND \d+\s*)\Z/m
|
13
|
+
prefix, subselect, suffix = [$1, $2, $3]
|
14
|
+
add_lock!(subselect, options)
|
15
|
+
return sql.replace(prefix + subselect + suffix)
|
16
|
+
end
|
17
|
+
unless sql =~ /\A(\s*SELECT\s.*?)(\sFROM\s)(.*?)(\sWHERE\s.*|)\Z/mi
|
18
|
+
# If you get this error, this driver probably needs to be fixed.
|
19
|
+
raise NotImplementedError, "Don't know how to add_lock! to SQL statement: #{sql.inspect}"
|
20
|
+
end
|
21
|
+
select_clause, from_word, from_tables, where_clause = [$1, $2, $3, $4]
|
22
|
+
with_clause = options[:lock].is_a?(String) ? " #{options[:lock]} " : " WITH(ROWLOCK,UPDLOCK) "
|
23
|
+
|
24
|
+
# Split the FROM clause into its constituent tables, and add the with clause after each one.
|
25
|
+
new_from_tables = []
|
26
|
+
s = StringScanner.new(from_tables)
|
27
|
+
until s.eos?
|
28
|
+
prev_pos = s.pos
|
29
|
+
if s.scan_until(/,|(INNER\s+JOIN|CROSS\s+JOIN|(LEFT|RIGHT|FULL)(\s+OUTER)?\s+JOIN)\s+/mi)
|
30
|
+
join_operand = s.pre_match[prev_pos..-1]
|
31
|
+
join_operator = s.matched
|
32
|
+
else
|
33
|
+
join_operand = s.rest
|
34
|
+
join_operator = ""
|
35
|
+
s.terminate
|
36
|
+
end
|
37
|
+
|
38
|
+
# At this point, we have something like:
|
39
|
+
# join_operand == "appointments "
|
40
|
+
# join_operator == "INNER JOIN "
|
41
|
+
# or:
|
42
|
+
# join_operand == "appointment_details AS d1 ON appointments.[id] = d1.[appointment_id]"
|
43
|
+
# join_operator == ""
|
44
|
+
if join_operand =~ /\A(.*)(\s+ON\s+.*)\Z/mi
|
45
|
+
table_spec, on_clause = [$1, $2]
|
46
|
+
else
|
47
|
+
table_spec = join_operand
|
48
|
+
on_clause = ""
|
49
|
+
end
|
50
|
+
|
51
|
+
# Add the "WITH(ROWLOCK,UPDLOCK)" option to the table specification
|
52
|
+
table_spec << with_clause unless table_spec =~ /\A\(\s*SELECT\s+/mi # HACK - this parser isn't so great
|
53
|
+
join_operand = table_spec + on_clause
|
54
|
+
|
55
|
+
# So now we have something like:
|
56
|
+
# join_operand == "appointments WITH(ROWLOCK,UPDLOCK) "
|
57
|
+
# join_operator == "INNER JOIN "
|
58
|
+
# or:
|
59
|
+
# join_operand == "appointment_details AS d1 WITH(ROWLOCK,UPDLOCK) ON appointments.[id] = d1.[appointment_id]"
|
60
|
+
# join_operator == ""
|
61
|
+
|
62
|
+
new_from_tables << join_operand
|
63
|
+
new_from_tables << join_operator
|
64
|
+
end
|
65
|
+
sql.replace([select_clause, from_word, new_from_tables, where_clause].flatten.join)
|
66
|
+
end
|
67
|
+
sql
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|