activerecord-sqlserver-adapter 3.2.18 → 4.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG +4 -28
- data/VERSION +1 -1
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +2 -7
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +6 -9
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +3 -25
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +4 -14
- data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +1 -3
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +2 -4
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +74 -80
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +10 -14
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +24 -15
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +24 -19
- data/lib/active_record/connection_adapters/sqlserver/schema_creation.rb +28 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +118 -77
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +10 -13
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +8 -11
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +2 -5
- data/lib/active_record/connection_adapters/sqlserver/table_definition.rb +23 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +4 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +121 -247
- data/lib/active_record/connection_adapters/sqlserver_column.rb +116 -0
- data/lib/active_record/sqlserver_base.rb +28 -0
- data/lib/active_record/sqlserver_test_case.rb +17 -0
- data/lib/arel/arel_sqlserver.rb +5 -0
- data/lib/arel/nodes_sqlserver.rb +14 -0
- data/lib/arel/select_manager_sqlserver.rb +62 -0
- data/lib/arel/visitors/sqlserver.rb +251 -188
- metadata +32 -10
- data/lib/active_record/connection_adapters/sqlserver/core_ext/database_statements.rb +0 -97
@@ -5,54 +5,52 @@ module ActiveRecord
|
|
5
5
|
module ConnectionAdapters
|
6
6
|
module Sqlserver
|
7
7
|
module Showplan
|
8
|
-
|
9
8
|
OPTION_ALL = 'SHOWPLAN_ALL'
|
10
9
|
OPTION_TEXT = 'SHOWPLAN_TEXT'
|
11
10
|
OPTION_XML = 'SHOWPLAN_XML'
|
12
11
|
OPTIONS = [OPTION_ALL, OPTION_TEXT, OPTION_XML]
|
13
|
-
|
12
|
+
|
14
13
|
def explain(arel, binds = [])
|
15
14
|
sql = to_sql(arel)
|
16
15
|
result = with_showplan_on { do_exec_query(sql, 'EXPLAIN', binds) }
|
17
16
|
printer = showplan_printer.new(result)
|
18
17
|
printer.pp
|
19
18
|
end
|
20
|
-
|
21
|
-
|
19
|
+
|
22
20
|
protected
|
23
|
-
|
21
|
+
|
24
22
|
def with_showplan_on
|
25
23
|
set_showplan_option(true)
|
26
24
|
yield
|
27
25
|
ensure
|
28
26
|
set_showplan_option(false)
|
29
27
|
end
|
30
|
-
|
28
|
+
|
31
29
|
def set_showplan_option(enable = true)
|
32
30
|
sql = "SET #{option} #{enable ? 'ON' : 'OFF'}"
|
33
31
|
raw_connection_do(sql)
|
34
|
-
rescue Exception
|
32
|
+
rescue Exception
|
35
33
|
raise ActiveRecordError, "#{option} could not be turned #{enable ? 'ON' : 'OFF'}, perhaps you do not have SHOWPLAN permissions?"
|
36
34
|
end
|
37
|
-
|
35
|
+
|
38
36
|
def option
|
39
37
|
(SQLServerAdapter.showplan_option || OPTION_ALL).tap do |opt|
|
40
38
|
raise(ArgumentError, "Unknown SHOWPLAN option #{opt.inspect} found.") if OPTIONS.exclude?(opt)
|
41
39
|
end
|
42
40
|
end
|
43
|
-
|
41
|
+
|
44
42
|
def showplan_all?
|
45
43
|
option == OPTION_ALL
|
46
44
|
end
|
47
|
-
|
45
|
+
|
48
46
|
def showplan_text?
|
49
47
|
option == OPTION_TEXT
|
50
48
|
end
|
51
|
-
|
49
|
+
|
52
50
|
def showplan_xml?
|
53
51
|
option == OPTION_XML
|
54
52
|
end
|
55
|
-
|
53
|
+
|
56
54
|
def showplan_printer
|
57
55
|
case option
|
58
56
|
when OPTION_XML then PrinterXml
|
@@ -60,7 +58,6 @@ module ActiveRecord
|
|
60
58
|
else PrinterTable
|
61
59
|
end
|
62
60
|
end
|
63
|
-
|
64
61
|
end
|
65
62
|
end
|
66
63
|
end
|
@@ -3,17 +3,16 @@ module ActiveRecord
|
|
3
3
|
module Sqlserver
|
4
4
|
module Showplan
|
5
5
|
class PrinterTable
|
6
|
-
|
7
6
|
cattr_accessor :max_column_width, :cell_padding
|
8
7
|
self.max_column_width = 50
|
9
8
|
self.cell_padding = 1
|
10
|
-
|
9
|
+
|
11
10
|
attr_reader :result
|
12
|
-
|
11
|
+
|
13
12
|
def initialize(result)
|
14
13
|
@result = result
|
15
14
|
end
|
16
|
-
|
15
|
+
|
17
16
|
def pp
|
18
17
|
@widths = compute_column_widths
|
19
18
|
@separator = build_separator
|
@@ -27,7 +26,7 @@ module ActiveRecord
|
|
27
26
|
pp << @separator
|
28
27
|
pp.join("\n") + "\n"
|
29
28
|
end
|
30
|
-
|
29
|
+
|
31
30
|
private
|
32
31
|
|
33
32
|
def compute_column_widths
|
@@ -40,11 +39,11 @@ module ActiveRecord
|
|
40
39
|
end
|
41
40
|
end
|
42
41
|
end
|
43
|
-
|
42
|
+
|
44
43
|
def build_separator
|
45
|
-
'+' + @widths.map {|w| '-' * (w + (cell_padding*2))}.join('+') + '+'
|
44
|
+
'+' + @widths.map { |w| '-' * (w + (cell_padding * 2)) }.join('+') + '+'
|
46
45
|
end
|
47
|
-
|
46
|
+
|
48
47
|
def build_cells(items)
|
49
48
|
cells = []
|
50
49
|
items.each_with_index do |item, i|
|
@@ -52,7 +51,7 @@ module ActiveRecord
|
|
52
51
|
end
|
53
52
|
"| #{cells.join(' | ')} |"
|
54
53
|
end
|
55
|
-
|
54
|
+
|
56
55
|
def cast_item(item)
|
57
56
|
case item
|
58
57
|
when NilClass then 'NULL'
|
@@ -60,9 +59,7 @@ module ActiveRecord
|
|
60
59
|
else item.to_s.truncate(max_column_width)
|
61
60
|
end
|
62
61
|
end
|
63
|
-
|
64
62
|
end
|
65
|
-
|
66
63
|
end
|
67
64
|
end
|
68
65
|
end
|
@@ -3,22 +3,19 @@ module ActiveRecord
|
|
3
3
|
module Sqlserver
|
4
4
|
module Showplan
|
5
5
|
class PrinterXml
|
6
|
-
|
7
6
|
def initialize(result)
|
8
7
|
@result = result
|
9
8
|
end
|
10
|
-
|
9
|
+
|
11
10
|
def pp
|
12
11
|
xml = @result.rows.first.first
|
13
12
|
if defined?(Nokogiri)
|
14
|
-
Nokogiri::XML(xml).to_xml :
|
13
|
+
Nokogiri::XML(xml).to_xml indent: 2, encoding: 'UTF-8'
|
15
14
|
else
|
16
15
|
xml
|
17
16
|
end
|
18
17
|
end
|
19
|
-
|
20
18
|
end
|
21
|
-
|
22
19
|
end
|
23
20
|
end
|
24
21
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
5
|
+
def uuid(name, options = {})
|
6
|
+
column(name, 'uniqueidentifier', options)
|
7
|
+
end
|
8
|
+
|
9
|
+
def primary_key(name, type = :primary_key, options = {})
|
10
|
+
return super unless type == :uuid
|
11
|
+
options[:default] = options.fetch(:default, 'NEWID()')
|
12
|
+
options[:primary_key] = true
|
13
|
+
column name, type, options
|
14
|
+
end
|
15
|
+
|
16
|
+
def column(name, type = nil, options = {})
|
17
|
+
super
|
18
|
+
self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -2,31 +2,25 @@ module ActiveRecord
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
module Sqlserver
|
4
4
|
class Utils
|
5
|
-
|
6
5
|
class << self
|
7
|
-
|
8
6
|
def unquote_string(string)
|
9
7
|
string.to_s.gsub(/\'\'/, "'")
|
10
8
|
end
|
11
|
-
|
9
|
+
|
12
10
|
def unqualify_table_name(table_name)
|
13
|
-
table_name.to_s.split('.').last.tr('[]','')
|
11
|
+
table_name.to_s.split('.').last.tr('[]', '')
|
14
12
|
end
|
15
13
|
|
16
14
|
def unqualify_table_schema(table_name)
|
17
|
-
table_name.to_s.split('.')[-2].gsub(/[\[\]]/,'') rescue nil
|
15
|
+
table_name.to_s.split('.')[-2].gsub(/[\[\]]/, '') rescue nil
|
18
16
|
end
|
19
17
|
|
20
18
|
def unqualify_db_name(table_name)
|
21
19
|
table_names = table_name.to_s.split('.')
|
22
|
-
table_names.length == 3 ? table_names.first.tr('[]','') : nil
|
20
|
+
table_names.length == 3 ? table_names.first.tr('[]', '') : nil
|
23
21
|
end
|
24
|
-
|
25
22
|
end
|
26
|
-
|
27
23
|
end
|
28
24
|
end
|
29
25
|
end
|
30
26
|
end
|
31
|
-
|
32
|
-
|
@@ -1,12 +1,12 @@
|
|
1
1
|
require 'base64'
|
2
|
-
require 'arel/
|
2
|
+
require 'arel/arel_sqlserver'
|
3
|
+
require 'arel/visitors/bind_visitor'
|
3
4
|
require 'active_record'
|
4
5
|
require 'active_record/base'
|
5
6
|
require 'active_support/concern'
|
6
7
|
require 'active_support/core_ext/string'
|
7
8
|
require 'active_record/connection_adapters/abstract_adapter'
|
8
9
|
require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
|
9
|
-
require 'active_record/connection_adapters/sqlserver/core_ext/database_statements'
|
10
10
|
require 'active_record/connection_adapters/sqlserver/core_ext/explain'
|
11
11
|
require 'active_record/connection_adapters/sqlserver/core_ext/explain_subscriber'
|
12
12
|
require 'active_record/connection_adapters/sqlserver/core_ext/relation'
|
@@ -14,163 +14,19 @@ require 'active_record/connection_adapters/sqlserver/database_limits'
|
|
14
14
|
require 'active_record/connection_adapters/sqlserver/database_statements'
|
15
15
|
require 'active_record/connection_adapters/sqlserver/errors'
|
16
16
|
require 'active_record/connection_adapters/sqlserver/schema_cache'
|
17
|
+
require 'active_record/connection_adapters/sqlserver/schema_creation'
|
17
18
|
require 'active_record/connection_adapters/sqlserver/schema_statements'
|
18
19
|
require 'active_record/connection_adapters/sqlserver/showplan'
|
20
|
+
require 'active_record/connection_adapters/sqlserver/table_definition'
|
19
21
|
require 'active_record/connection_adapters/sqlserver/quoting'
|
20
22
|
require 'active_record/connection_adapters/sqlserver/utils'
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
class Base
|
25
|
-
|
26
|
-
def self.sqlserver_connection(config) #:nodoc:
|
27
|
-
config = config.symbolize_keys
|
28
|
-
config.reverse_merge! :mode => :dblib
|
29
|
-
mode = config[:mode].to_s.downcase.underscore.to_sym
|
30
|
-
case mode
|
31
|
-
when :dblib
|
32
|
-
require 'tiny_tds'
|
33
|
-
when :odbc
|
34
|
-
raise ArgumentError, 'Missing :dsn configuration.' unless config.has_key?(:dsn)
|
35
|
-
require 'odbc'
|
36
|
-
require 'active_record/connection_adapters/sqlserver/core_ext/odbc'
|
37
|
-
else
|
38
|
-
raise ArgumentError, "Unknown connection mode in #{config.inspect}."
|
39
|
-
end
|
40
|
-
ConnectionAdapters::SQLServerAdapter.new(nil, logger, nil, config.merge(:mode=>mode))
|
41
|
-
end
|
42
|
-
|
43
|
-
protected
|
44
|
-
|
45
|
-
def self.did_retry_sqlserver_connection(connection,count)
|
46
|
-
logger.info "CONNECTION RETRY: #{connection.class.name} retry ##{count}."
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.did_lose_sqlserver_connection(connection)
|
50
|
-
logger.info "CONNECTION LOST: #{connection.class.name}"
|
51
|
-
end
|
52
|
-
|
53
|
-
end
|
24
|
+
require 'active_record/sqlserver_base'
|
25
|
+
require 'active_record/connection_adapters/sqlserver_column'
|
54
26
|
|
27
|
+
module ActiveRecord
|
55
28
|
module ConnectionAdapters
|
56
|
-
|
57
|
-
class SQLServerColumn < Column
|
58
|
-
|
59
|
-
def initialize(name, default, sql_type = nil, null = true, sqlserver_options = {})
|
60
|
-
@sqlserver_options = sqlserver_options.symbolize_keys
|
61
|
-
super(name, default, sql_type, null)
|
62
|
-
@primary = @sqlserver_options[:is_identity] || @sqlserver_options[:is_primary]
|
63
|
-
end
|
64
|
-
|
65
|
-
class << self
|
66
|
-
|
67
|
-
def string_to_binary(value)
|
68
|
-
"0x#{value.unpack("H*")[0]}"
|
69
|
-
end
|
70
|
-
|
71
|
-
def binary_to_string(value)
|
72
|
-
value =~ /[^[:xdigit:]]/ ? value : [value].pack('H*')
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
def is_identity?
|
78
|
-
@sqlserver_options[:is_identity]
|
79
|
-
end
|
80
|
-
|
81
|
-
def is_primary?
|
82
|
-
@sqlserver_options[:is_primary]
|
83
|
-
end
|
84
|
-
|
85
|
-
def is_utf8?
|
86
|
-
!!(@sql_type =~ /nvarchar|ntext|nchar/i)
|
87
|
-
end
|
88
|
-
|
89
|
-
def is_integer?
|
90
|
-
!!(@sql_type =~ /int/i)
|
91
|
-
end
|
92
|
-
|
93
|
-
def is_real?
|
94
|
-
!!(@sql_type =~ /real/i)
|
95
|
-
end
|
96
|
-
|
97
|
-
def sql_type_for_statement
|
98
|
-
if is_integer? || is_real?
|
99
|
-
sql_type.sub(/\((\d+)?\)/,'')
|
100
|
-
else
|
101
|
-
sql_type
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def default_function
|
106
|
-
@sqlserver_options[:default_function]
|
107
|
-
end
|
108
|
-
|
109
|
-
def table_name
|
110
|
-
@sqlserver_options[:table_name]
|
111
|
-
end
|
112
|
-
|
113
|
-
def table_klass
|
114
|
-
@table_klass ||= begin
|
115
|
-
table_name.classify.constantize
|
116
|
-
rescue StandardError, NameError, LoadError
|
117
|
-
nil
|
118
|
-
end
|
119
|
-
(@table_klass && @table_klass < ActiveRecord::Base) ? @table_klass : nil
|
120
|
-
end
|
121
|
-
|
122
|
-
def database_year
|
123
|
-
@sqlserver_options[:database_year]
|
124
|
-
end
|
125
|
-
|
126
|
-
|
127
|
-
private
|
128
|
-
|
129
|
-
def extract_limit(sql_type)
|
130
|
-
case sql_type
|
131
|
-
when /^smallint/i
|
132
|
-
2
|
133
|
-
when /^int/i
|
134
|
-
4
|
135
|
-
when /^bigint/i
|
136
|
-
8
|
137
|
-
when /\(max\)/, /decimal/, /numeric/
|
138
|
-
nil
|
139
|
-
else
|
140
|
-
super
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
def simplified_type(field_type)
|
145
|
-
case field_type
|
146
|
-
when /real/i then :float
|
147
|
-
when /money/i then :decimal
|
148
|
-
when /image/i then :binary
|
149
|
-
when /bit/i then :boolean
|
150
|
-
when /uniqueidentifier/i then :string
|
151
|
-
when /datetime/i then simplified_datetime
|
152
|
-
when /varchar\(max\)/ then :text
|
153
|
-
when /timestamp/ then :binary
|
154
|
-
else super
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
def simplified_datetime
|
159
|
-
if database_year >= 2008
|
160
|
-
:datetime
|
161
|
-
elsif table_klass && table_klass.coerced_sqlserver_date_columns.include?(name)
|
162
|
-
:date
|
163
|
-
elsif table_klass && table_klass.coerced_sqlserver_time_columns.include?(name)
|
164
|
-
:time
|
165
|
-
else
|
166
|
-
:datetime
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
end #class SQLServerColumn
|
171
|
-
|
172
29
|
class SQLServerAdapter < AbstractAdapter
|
173
|
-
|
174
30
|
include Sqlserver::Quoting
|
175
31
|
include Sqlserver::DatabaseStatements
|
176
32
|
include Sqlserver::Showplan
|
@@ -178,20 +34,22 @@ module ActiveRecord
|
|
178
34
|
include Sqlserver::DatabaseLimits
|
179
35
|
include Sqlserver::Errors
|
180
36
|
|
181
|
-
VERSION = File.read(File.expand_path(
|
37
|
+
VERSION = File.read(File.expand_path('../../../../VERSION', __FILE__)).strip
|
182
38
|
ADAPTER_NAME = 'SQLServer'.freeze
|
183
39
|
DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+"?(\d{4}|\w+)"?/
|
184
|
-
SUPPORTED_VERSIONS = [2005,2008,2010,2011,2012
|
40
|
+
SUPPORTED_VERSIONS = [2005, 2008, 2010, 2011, 2012]
|
185
41
|
|
186
42
|
attr_reader :database_version, :database_year, :spid, :product_level, :product_version, :edition
|
187
43
|
|
188
44
|
cattr_accessor :native_text_database_type, :native_binary_database_type, :native_string_database_type,
|
189
|
-
:enable_default_unicode_types, :auto_connect, :
|
190
|
-
:
|
191
|
-
:showplan_option
|
45
|
+
:enable_default_unicode_types, :auto_connect, :cs_equality_operator,
|
46
|
+
:lowercase_schema_reflection, :auto_connect_duration, :showplan_option
|
192
47
|
|
193
48
|
self.enable_default_unicode_types = true
|
194
49
|
|
50
|
+
class BindSubstitution < Arel::Visitors::SQLServer # :nodoc:
|
51
|
+
include Arel::Visitors::BindVisitor
|
52
|
+
end
|
195
53
|
|
196
54
|
def initialize(connection, logger, pool, config)
|
197
55
|
super(connection, logger, pool)
|
@@ -207,12 +65,10 @@ module ActiveRecord
|
|
207
65
|
if @database_version =~ /Azure/i
|
208
66
|
@sqlserver_azure = true
|
209
67
|
@database_version.match(/\s-\s([0-9.]+)/)[1]
|
210
|
-
year =
|
211
|
-
elsif @database_version =~ /vNext/i
|
212
|
-
year = 2016
|
68
|
+
year = 2012
|
213
69
|
else
|
214
70
|
year = DATABASE_VERSION_REGEXP.match(@database_version)[1]
|
215
|
-
year ==
|
71
|
+
year == 'Denali' ? 2011 : year.to_i
|
216
72
|
end
|
217
73
|
rescue
|
218
74
|
0
|
@@ -222,7 +78,7 @@ module ActiveRecord
|
|
222
78
|
@edition = select_value "SELECT CAST(SERVERPROPERTY('edition') AS VARCHAR(128))", 'SCHEMA'
|
223
79
|
initialize_dateformatter
|
224
80
|
use_database
|
225
|
-
unless
|
81
|
+
unless @sqlserver_azure == true || SUPPORTED_VERSIONS.include?(@database_year)
|
226
82
|
raise NotImplementedError, "Currently, only #{SUPPORTED_VERSIONS.to_sentence} are supported. We got back #{@database_version}."
|
227
83
|
end
|
228
84
|
end
|
@@ -261,6 +117,10 @@ module ActiveRecord
|
|
261
117
|
true
|
262
118
|
end
|
263
119
|
|
120
|
+
def supports_partial_index?
|
121
|
+
@database_year >= 2008
|
122
|
+
end
|
123
|
+
|
264
124
|
def supports_explain?
|
265
125
|
true
|
266
126
|
end
|
@@ -279,19 +139,21 @@ module ActiveRecord
|
|
279
139
|
when :dblib
|
280
140
|
return @connection.active?
|
281
141
|
end
|
282
|
-
raw_connection_do(
|
142
|
+
raw_connection_do('SELECT 1')
|
283
143
|
true
|
284
144
|
rescue *lost_connection_exceptions
|
285
145
|
false
|
286
146
|
end
|
287
147
|
|
288
148
|
def reconnect!
|
149
|
+
reset_transaction
|
289
150
|
disconnect!
|
290
151
|
connect
|
291
152
|
active?
|
292
153
|
end
|
293
154
|
|
294
155
|
def disconnect!
|
156
|
+
reset_transaction
|
295
157
|
@spid = nil
|
296
158
|
case @connection_options[:mode]
|
297
159
|
when :dblib
|
@@ -302,18 +164,22 @@ module ActiveRecord
|
|
302
164
|
end
|
303
165
|
|
304
166
|
def reset!
|
305
|
-
remove_database_connections_and_rollback {
|
167
|
+
remove_database_connections_and_rollback {}
|
306
168
|
end
|
307
169
|
|
308
170
|
# === Abstract Adapter (Misc Support) =========================== #
|
309
171
|
|
310
172
|
def pk_and_sequence_for(table_name)
|
311
|
-
|
312
|
-
|
173
|
+
pk = primary_key(table_name)
|
174
|
+
pk ? [pk, nil] : nil
|
313
175
|
end
|
314
176
|
|
315
177
|
def primary_key(table_name)
|
316
|
-
identity_column(table_name).try(:name) || schema_cache.columns
|
178
|
+
identity_column(table_name).try(:name) || schema_cache.columns(table_name).find(&:is_primary?).try(:name)
|
179
|
+
end
|
180
|
+
|
181
|
+
def schema_creation
|
182
|
+
Sqlserver::SchemaCreation.new self
|
317
183
|
end
|
318
184
|
|
319
185
|
# === SQLServer Specific (DB Reflection) ======================== #
|
@@ -358,11 +224,6 @@ module ActiveRecord
|
|
358
224
|
@@auto_connect_duration ||= 10
|
359
225
|
end
|
360
226
|
|
361
|
-
def retry_deadlock_victim
|
362
|
-
@@retry_deadlock_victim.is_a?(FalseClass) ? false : true
|
363
|
-
end
|
364
|
-
alias :retry_deadlock_victim? :retry_deadlock_victim
|
365
|
-
|
366
227
|
def native_string_database_type
|
367
228
|
@@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
|
368
229
|
end
|
@@ -394,13 +255,13 @@ module ActiveRecord
|
|
394
255
|
def translate_exception(e, message)
|
395
256
|
case message
|
396
257
|
when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
|
397
|
-
RecordNotUnique.new(message,e)
|
258
|
+
RecordNotUnique.new(message, e)
|
398
259
|
when /conflicted with the foreign key constraint/i
|
399
|
-
InvalidForeignKey.new(message,e)
|
260
|
+
InvalidForeignKey.new(message, e)
|
400
261
|
when /has been chosen as the deadlock victim/i
|
401
|
-
DeadlockVictim.new(message,e)
|
262
|
+
DeadlockVictim.new(message, e)
|
402
263
|
when *lost_connection_messages
|
403
|
-
LostConnection.new(message,e)
|
264
|
+
LostConnection.new(message, e)
|
404
265
|
else
|
405
266
|
super
|
406
267
|
end
|
@@ -412,64 +273,84 @@ module ActiveRecord
|
|
412
273
|
config = @connection_options
|
413
274
|
@connection = case config[:mode]
|
414
275
|
when :dblib
|
415
|
-
|
416
|
-
login_timeout = config[:login_timeout].present? ? config[:login_timeout].to_i : nil
|
417
|
-
timeout = config[:timeout].present? ? config[:timeout].to_i/1000 : nil
|
418
|
-
encoding = config[:encoding].present? ? config[:encoding] : nil
|
419
|
-
TinyTds::Client.new({
|
420
|
-
:dataserver => config[:dataserver],
|
421
|
-
:host => config[:host],
|
422
|
-
:port => config[:port],
|
423
|
-
:username => config[:username],
|
424
|
-
:password => config[:password],
|
425
|
-
:database => config[:database],
|
426
|
-
:tds_version => config[:tds_version],
|
427
|
-
:appname => appname,
|
428
|
-
:login_timeout => login_timeout,
|
429
|
-
:timeout => timeout,
|
430
|
-
:encoding => encoding,
|
431
|
-
:azure => config[:azure]
|
432
|
-
}).tap do |client|
|
433
|
-
if config[:azure]
|
434
|
-
client.execute("SET ANSI_NULLS ON").do
|
435
|
-
client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
|
436
|
-
client.execute("SET ANSI_NULL_DFLT_ON ON").do
|
437
|
-
client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
|
438
|
-
client.execute("SET ANSI_PADDING ON").do
|
439
|
-
client.execute("SET QUOTED_IDENTIFIER ON")
|
440
|
-
client.execute("SET ANSI_WARNINGS ON").do
|
441
|
-
else
|
442
|
-
client.execute("SET ANSI_DEFAULTS ON").do
|
443
|
-
client.execute("SET CURSOR_CLOSE_ON_COMMIT OFF").do
|
444
|
-
client.execute("SET IMPLICIT_TRANSACTIONS OFF").do
|
445
|
-
end
|
446
|
-
client.execute("SET TEXTSIZE 2147483647").do
|
447
|
-
client.execute("SET CONCAT_NULL_YIELDS_NULL ON").do
|
448
|
-
end
|
276
|
+
dblib_connect(config)
|
449
277
|
when :odbc
|
450
|
-
|
451
|
-
driver = ODBC::Driver.new.tap do |d|
|
452
|
-
d.name = config[:dsn_name] || 'Driver1'
|
453
|
-
d.attrs = config[:dsn].split(';').map{ |atr| atr.split('=') }.reject{ |kv| kv.size != 2 }.inject({}){ |h,kv| k,v = kv ; h[k] = v ; h }
|
454
|
-
end
|
455
|
-
ODBC::Database.new.drvconnect(driver)
|
456
|
-
else
|
457
|
-
ODBC.connect config[:dsn], config[:username], config[:password]
|
458
|
-
end.tap do |c|
|
459
|
-
begin
|
460
|
-
c.use_time = true
|
461
|
-
c.use_utc = ActiveRecord::Base.default_timezone == :utc
|
462
|
-
rescue Exception => e
|
463
|
-
warn "Ruby ODBC v0.99992 or higher is required."
|
464
|
-
end
|
465
|
-
end
|
278
|
+
odbc_connect(config)
|
466
279
|
end
|
467
|
-
@spid = _raw_select(
|
280
|
+
@spid = _raw_select('SELECT @@SPID', fetch: :rows).first.first
|
468
281
|
configure_connection
|
469
282
|
rescue
|
470
283
|
raise unless @auto_connecting
|
471
284
|
end
|
472
285
|
|
286
|
+
def dblib_connect(config)
|
287
|
+
TinyTds::Client.new(
|
288
|
+
dataserver: config[:dataserver],
|
289
|
+
host: config[:host],
|
290
|
+
port: config[:port],
|
291
|
+
username: config[:username],
|
292
|
+
password: config[:password],
|
293
|
+
database: config[:database],
|
294
|
+
tds_version: config[:tds_version],
|
295
|
+
appname: appname(config),
|
296
|
+
login_timeout: login_timeout(config),
|
297
|
+
timeout: timeout(config),
|
298
|
+
encoding: encoding(config),
|
299
|
+
azure: config[:azure]
|
300
|
+
).tap do |client|
|
301
|
+
if config[:azure]
|
302
|
+
client.execute('SET ANSI_NULLS ON').do
|
303
|
+
client.execute('SET CURSOR_CLOSE_ON_COMMIT OFF').do
|
304
|
+
client.execute('SET ANSI_NULL_DFLT_ON ON').do
|
305
|
+
client.execute('SET IMPLICIT_TRANSACTIONS OFF').do
|
306
|
+
client.execute('SET ANSI_PADDING ON').do
|
307
|
+
client.execute('SET QUOTED_IDENTIFIER ON')
|
308
|
+
client.execute('SET ANSI_WARNINGS ON').do
|
309
|
+
else
|
310
|
+
client.execute('SET ANSI_DEFAULTS ON').do
|
311
|
+
client.execute('SET CURSOR_CLOSE_ON_COMMIT OFF').do
|
312
|
+
client.execute('SET IMPLICIT_TRANSACTIONS OFF').do
|
313
|
+
end
|
314
|
+
client.execute('SET TEXTSIZE 2147483647').do
|
315
|
+
client.execute('SET CONCAT_NULL_YIELDS_NULL ON').do
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def appname(config)
|
320
|
+
config[:appname] || configure_application_name || Rails.application.class.name.split('::').first rescue nil
|
321
|
+
end
|
322
|
+
|
323
|
+
def login_timeout(config)
|
324
|
+
config[:login_timeout].present? ? config[:login_timeout].to_i : nil
|
325
|
+
end
|
326
|
+
|
327
|
+
def timeout(config)
|
328
|
+
config[:timeout].present? ? config[:timeout].to_i / 1000 : nil
|
329
|
+
end
|
330
|
+
|
331
|
+
def encoding(config)
|
332
|
+
config[:encoding].present? ? config[:encoding] : nil
|
333
|
+
end
|
334
|
+
|
335
|
+
def odbc_connect(config)
|
336
|
+
if config[:dsn].include?(';')
|
337
|
+
driver = ODBC::Driver.new.tap do |d|
|
338
|
+
d.name = config[:dsn_name] || 'Driver1'
|
339
|
+
d.attrs = config[:dsn].split(';').map { |atr| atr.split('=') }.reject { |kv| kv.size != 2 }.reduce({}) { |a, e| k, v = e ; a[k] = v ; a }
|
340
|
+
end
|
341
|
+
ODBC::Database.new.drvconnect(driver)
|
342
|
+
else
|
343
|
+
ODBC.connect config[:dsn], config[:username], config[:password]
|
344
|
+
end.tap do |c|
|
345
|
+
begin
|
346
|
+
c.use_time = true
|
347
|
+
c.use_utc = ActiveRecord::Base.default_timezone == :utc
|
348
|
+
rescue Exception
|
349
|
+
warn 'Ruby ODBC v0.99992 or higher is required.'
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
473
354
|
# Override this method so every connection can be configured to your needs.
|
474
355
|
# For example:
|
475
356
|
# raw_connection_do "SET TEXTSIZE #{64.megabytes}"
|
@@ -486,32 +367,29 @@ module ActiveRecord
|
|
486
367
|
def initialize_dateformatter
|
487
368
|
@database_dateformat = user_options_dateformat
|
488
369
|
a, b, c = @database_dateformat.each_char.to_a
|
489
|
-
[a,b,c].each { |f| f.upcase! if f == 'y' }
|
370
|
+
[a, b, c].each { |f| f.upcase! if f == 'y' }
|
490
371
|
dateformat = "%#{a}-%#{b}-%#{c}"
|
491
372
|
::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
|
492
373
|
::Time::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
|
493
374
|
end
|
494
375
|
|
495
|
-
def remove_database_connections_and_rollback(database=nil)
|
376
|
+
def remove_database_connections_and_rollback(database = nil)
|
496
377
|
database ||= current_database
|
497
|
-
do_execute "ALTER DATABASE #{
|
378
|
+
do_execute "ALTER DATABASE #{quote_database_name(database)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
|
498
379
|
begin
|
499
380
|
yield
|
500
381
|
ensure
|
501
|
-
do_execute "ALTER DATABASE #{
|
382
|
+
do_execute "ALTER DATABASE #{quote_database_name(database)} SET MULTI_USER"
|
502
383
|
end if block_given?
|
503
384
|
end
|
504
385
|
|
505
386
|
def with_sqlserver_error_handling
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
when LostConnection; retry if auto_reconnected?
|
511
|
-
when DeadlockVictim; retry if retry_deadlock_victim? && open_transactions == 0
|
512
|
-
end
|
513
|
-
raise
|
387
|
+
yield
|
388
|
+
rescue Exception => e
|
389
|
+
case translate_exception(e, e.message)
|
390
|
+
when LostConnection then retry if auto_reconnected?
|
514
391
|
end
|
392
|
+
raise
|
515
393
|
end
|
516
394
|
|
517
395
|
def disable_auto_reconnect
|
@@ -527,9 +405,9 @@ module ActiveRecord
|
|
527
405
|
count = 0
|
528
406
|
while count <= (auto_connect_duration / 2)
|
529
407
|
result = reconnect!
|
530
|
-
ActiveRecord::Base.did_retry_sqlserver_connection(self,count)
|
408
|
+
ActiveRecord::Base.did_retry_sqlserver_connection(self, count)
|
531
409
|
return true if result
|
532
|
-
sleep 2**
|
410
|
+
sleep 2**count
|
533
411
|
count += 1
|
534
412
|
end
|
535
413
|
ActiveRecord::Base.did_lose_sqlserver_connection(self)
|
@@ -537,10 +415,6 @@ module ActiveRecord
|
|
537
415
|
ensure
|
538
416
|
@auto_connecting = false
|
539
417
|
end
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
end #module ConnectionAdapters
|
544
|
-
|
545
|
-
end #module ActiveRecord
|
546
|
-
|
418
|
+
end # class SQLServerAdapter < AbstractAdapter
|
419
|
+
end # module ConnectionAdapters
|
420
|
+
end # module ActiveRecord
|