activerecord-jdbc-adapter 1.2.1 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|