sic-activerecord-sqlserver-adapter 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 +7 -0
- data/CHANGELOG +603 -0
- data/MIT-LICENSE +20 -0
- data/VERSION +1 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb +42 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain.rb +41 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/explain_subscriber.rb +4 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/odbc.rb +38 -0
- data/lib/active_record/connection_adapters/sqlserver/core_ext/relation.rb +19 -0
- data/lib/active_record/connection_adapters/sqlserver/database_limits.rb +49 -0
- data/lib/active_record/connection_adapters/sqlserver/database_statements.rb +458 -0
- data/lib/active_record/connection_adapters/sqlserver/errors.rb +36 -0
- data/lib/active_record/connection_adapters/sqlserver/quoting.rb +109 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_cache.rb +85 -0
- data/lib/active_record/connection_adapters/sqlserver/schema_statements.rb +395 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan.rb +67 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_table.rb +69 -0
- data/lib/active_record/connection_adapters/sqlserver/showplan/printer_xml.rb +25 -0
- data/lib/active_record/connection_adapters/sqlserver/utils.rb +32 -0
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +545 -0
- data/lib/active_record/sqlserver_test_case.rb +17 -0
- data/lib/activerecord-sqlserver-adapter.rb +1 -0
- data/lib/arel/select_manager_sqlserver.rb +64 -0
- data/lib/arel/visitors/sqlserver.rb +326 -0
- metadata +98 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'active_record/connection_adapters/sqlserver/showplan/printer_table'
|
2
|
+
require 'active_record/connection_adapters/sqlserver/showplan/printer_xml'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
module ConnectionAdapters
|
6
|
+
module Sqlserver
|
7
|
+
module Showplan
|
8
|
+
|
9
|
+
OPTION_ALL = 'SHOWPLAN_ALL'
|
10
|
+
OPTION_TEXT = 'SHOWPLAN_TEXT'
|
11
|
+
OPTION_XML = 'SHOWPLAN_XML'
|
12
|
+
OPTIONS = [OPTION_ALL, OPTION_TEXT, OPTION_XML]
|
13
|
+
|
14
|
+
def explain(arel, binds = [])
|
15
|
+
sql = to_sql(arel)
|
16
|
+
result = with_showplan_on { do_exec_query(sql, 'EXPLAIN', binds) }
|
17
|
+
printer = showplan_printer.new(result)
|
18
|
+
printer.pp
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def with_showplan_on
|
25
|
+
set_showplan_option(true)
|
26
|
+
yield
|
27
|
+
ensure
|
28
|
+
set_showplan_option(false)
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_showplan_option(enable = true)
|
32
|
+
sql = "SET #{option} #{enable ? 'ON' : 'OFF'}"
|
33
|
+
raw_connection_do(sql)
|
34
|
+
rescue Exception => e
|
35
|
+
raise ActiveRecordError, "#{option} could not be turned #{enable ? 'ON' : 'OFF'}, perhaps you do not have SHOWPLAN permissions?"
|
36
|
+
end
|
37
|
+
|
38
|
+
def option
|
39
|
+
(SQLServerAdapter.showplan_option || OPTION_ALL).tap do |opt|
|
40
|
+
raise(ArgumentError, "Unknown SHOWPLAN option #{opt.inspect} found.") if OPTIONS.exclude?(opt)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def showplan_all?
|
45
|
+
option == OPTION_ALL
|
46
|
+
end
|
47
|
+
|
48
|
+
def showplan_text?
|
49
|
+
option == OPTION_TEXT
|
50
|
+
end
|
51
|
+
|
52
|
+
def showplan_xml?
|
53
|
+
option == OPTION_XML
|
54
|
+
end
|
55
|
+
|
56
|
+
def showplan_printer
|
57
|
+
case option
|
58
|
+
when OPTION_XML then PrinterXml
|
59
|
+
when OPTION_ALL, OPTION_TEXT then PrinterTable
|
60
|
+
else PrinterTable
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module Showplan
|
5
|
+
class PrinterTable
|
6
|
+
|
7
|
+
cattr_accessor :max_column_width, :cell_padding
|
8
|
+
self.max_column_width = 50
|
9
|
+
self.cell_padding = 1
|
10
|
+
|
11
|
+
attr_reader :result
|
12
|
+
|
13
|
+
def initialize(result)
|
14
|
+
@result = result
|
15
|
+
end
|
16
|
+
|
17
|
+
def pp
|
18
|
+
@widths = compute_column_widths
|
19
|
+
@separator = build_separator
|
20
|
+
pp = []
|
21
|
+
pp << @separator
|
22
|
+
pp << build_cells(result.columns)
|
23
|
+
pp << @separator
|
24
|
+
result.rows.each do |row|
|
25
|
+
pp << build_cells(row)
|
26
|
+
end
|
27
|
+
pp << @separator
|
28
|
+
pp.join("\n") + "\n"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def compute_column_widths
|
34
|
+
[].tap do |computed_widths|
|
35
|
+
result.columns.each_with_index do |column, i|
|
36
|
+
cells_in_column = [column] + result.rows.map { |r| cast_item(r[i]) }
|
37
|
+
computed_width = cells_in_column.map(&:length).max
|
38
|
+
final_width = computed_width > max_column_width ? max_column_width : computed_width
|
39
|
+
computed_widths << final_width
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_separator
|
45
|
+
'+' + @widths.map {|w| '-' * (w + (cell_padding*2))}.join('+') + '+'
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_cells(items)
|
49
|
+
cells = []
|
50
|
+
items.each_with_index do |item, i|
|
51
|
+
cells << cast_item(item).ljust(@widths[i])
|
52
|
+
end
|
53
|
+
"| #{cells.join(' | ')} |"
|
54
|
+
end
|
55
|
+
|
56
|
+
def cast_item(item)
|
57
|
+
case item
|
58
|
+
when NilClass then 'NULL'
|
59
|
+
when Float then item.to_s.to(9)
|
60
|
+
else item.to_s.truncate(max_column_width)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
module Showplan
|
5
|
+
class PrinterXml
|
6
|
+
|
7
|
+
def initialize(result)
|
8
|
+
@result = result
|
9
|
+
end
|
10
|
+
|
11
|
+
def pp
|
12
|
+
xml = @result.rows.first.first
|
13
|
+
if defined?(Nokogiri)
|
14
|
+
Nokogiri::XML(xml).to_xml :indent => 2, :encoding => 'UTF-8'
|
15
|
+
else
|
16
|
+
xml
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sqlserver
|
4
|
+
class Utils
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def unquote_string(string)
|
9
|
+
string.to_s.gsub(/\'\'/, "'")
|
10
|
+
end
|
11
|
+
|
12
|
+
def unqualify_table_name(table_name)
|
13
|
+
table_name.to_s.split('.').last.tr('[]','')
|
14
|
+
end
|
15
|
+
|
16
|
+
def unqualify_table_schema(table_name)
|
17
|
+
table_name.to_s.split('.')[-2].gsub(/[\[\]]/,'') rescue nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def unqualify_db_name(table_name)
|
21
|
+
table_names = table_name.to_s.split('.')
|
22
|
+
table_names.length == 3 ? table_names.first.tr('[]','') : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
@@ -0,0 +1,545 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'arel/visitors/sqlserver'
|
3
|
+
require 'arel/visitors/bind_visitor'
|
4
|
+
require 'active_record'
|
5
|
+
require 'active_record/base'
|
6
|
+
require 'active_support/concern'
|
7
|
+
require 'active_support/core_ext/string'
|
8
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
9
|
+
require 'active_record/connection_adapters/sqlserver/core_ext/active_record'
|
10
|
+
require 'active_record/connection_adapters/sqlserver/core_ext/explain'
|
11
|
+
require 'active_record/connection_adapters/sqlserver/core_ext/explain_subscriber'
|
12
|
+
require 'active_record/connection_adapters/sqlserver/core_ext/relation'
|
13
|
+
require 'active_record/connection_adapters/sqlserver/database_limits'
|
14
|
+
require 'active_record/connection_adapters/sqlserver/database_statements'
|
15
|
+
require 'active_record/connection_adapters/sqlserver/errors'
|
16
|
+
require 'active_record/connection_adapters/sqlserver/schema_cache'
|
17
|
+
require 'active_record/connection_adapters/sqlserver/schema_statements'
|
18
|
+
require 'active_record/connection_adapters/sqlserver/showplan'
|
19
|
+
require 'active_record/connection_adapters/sqlserver/quoting'
|
20
|
+
require 'active_record/connection_adapters/sqlserver/utils'
|
21
|
+
|
22
|
+
module ActiveRecord
|
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
|
54
|
+
|
55
|
+
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
|
+
if value.encoding != Encoding::ASCII_8BIT
|
73
|
+
value = value.force_encoding(Encoding::ASCII_8BIT)
|
74
|
+
end
|
75
|
+
value
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
def is_identity?
|
81
|
+
@sqlserver_options[:is_identity]
|
82
|
+
end
|
83
|
+
|
84
|
+
def is_primary?
|
85
|
+
@sqlserver_options[:is_primary]
|
86
|
+
end
|
87
|
+
|
88
|
+
def is_utf8?
|
89
|
+
!!(@sql_type =~ /nvarchar|ntext|nchar/i)
|
90
|
+
end
|
91
|
+
|
92
|
+
def is_integer?
|
93
|
+
!!(@sql_type =~ /int/i)
|
94
|
+
end
|
95
|
+
|
96
|
+
def is_real?
|
97
|
+
!!(@sql_type =~ /real/i)
|
98
|
+
end
|
99
|
+
|
100
|
+
def sql_type_for_statement
|
101
|
+
if is_integer? || is_real?
|
102
|
+
sql_type.sub(/\((\d+)?\)/,'')
|
103
|
+
else
|
104
|
+
sql_type
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def default_function
|
109
|
+
@sqlserver_options[:default_function]
|
110
|
+
end
|
111
|
+
|
112
|
+
def table_name
|
113
|
+
@sqlserver_options[:table_name]
|
114
|
+
end
|
115
|
+
|
116
|
+
def table_klass
|
117
|
+
@table_klass ||= begin
|
118
|
+
table_name.classify.constantize
|
119
|
+
rescue StandardError, NameError, LoadError
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
(@table_klass && @table_klass < ActiveRecord::Base) ? @table_klass : nil
|
123
|
+
end
|
124
|
+
|
125
|
+
def database_year
|
126
|
+
@sqlserver_options[:database_year]
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def extract_limit(sql_type)
|
133
|
+
case sql_type
|
134
|
+
when /^smallint/i
|
135
|
+
2
|
136
|
+
when /^int/i
|
137
|
+
4
|
138
|
+
when /^bigint/i
|
139
|
+
8
|
140
|
+
when /\(max\)/, /decimal/, /numeric/
|
141
|
+
nil
|
142
|
+
else
|
143
|
+
super
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def simplified_type(field_type)
|
148
|
+
case field_type
|
149
|
+
when /real/i then :float
|
150
|
+
when /money/i then :decimal
|
151
|
+
when /image/i then :binary
|
152
|
+
when /bit/i then :boolean
|
153
|
+
when /uniqueidentifier/i then :string
|
154
|
+
when /datetime/i then simplified_datetime
|
155
|
+
when /varchar\(max\)/ then :text
|
156
|
+
when /timestamp/ then :binary
|
157
|
+
else super
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def simplified_datetime
|
162
|
+
if database_year >= 2008
|
163
|
+
:datetime
|
164
|
+
elsif table_klass && table_klass.coerced_sqlserver_date_columns.include?(name)
|
165
|
+
:date
|
166
|
+
elsif table_klass && table_klass.coerced_sqlserver_time_columns.include?(name)
|
167
|
+
:time
|
168
|
+
else
|
169
|
+
:datetime
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end #class SQLServerColumn
|
174
|
+
|
175
|
+
class SQLServerAdapter < AbstractAdapter
|
176
|
+
|
177
|
+
include Sqlserver::Quoting
|
178
|
+
include Sqlserver::DatabaseStatements
|
179
|
+
include Sqlserver::Showplan
|
180
|
+
include Sqlserver::SchemaStatements
|
181
|
+
include Sqlserver::DatabaseLimits
|
182
|
+
include Sqlserver::Errors
|
183
|
+
|
184
|
+
VERSION = File.read(File.expand_path("../../../../VERSION",__FILE__)).strip
|
185
|
+
ADAPTER_NAME = 'SQLServer'.freeze
|
186
|
+
DATABASE_VERSION_REGEXP = /Microsoft SQL Server\s+"?(\d{4}|\w+)"?/
|
187
|
+
SUPPORTED_VERSIONS = [2005,2008,2010,2011,2012]
|
188
|
+
|
189
|
+
attr_reader :database_version, :database_year, :spid, :product_level, :product_version, :edition
|
190
|
+
|
191
|
+
cattr_accessor :native_text_database_type, :native_binary_database_type, :native_string_database_type,
|
192
|
+
:enable_default_unicode_types, :auto_connect, :cs_equality_operator,
|
193
|
+
:lowercase_schema_reflection, :auto_connect_duration, :showplan_option
|
194
|
+
|
195
|
+
self.enable_default_unicode_types = true
|
196
|
+
|
197
|
+
class BindSubstitution < Arel::Visitors::SQLServer # :nodoc:
|
198
|
+
include Arel::Visitors::BindVisitor
|
199
|
+
end
|
200
|
+
|
201
|
+
def initialize(connection, logger, pool, config)
|
202
|
+
super(connection, logger, pool)
|
203
|
+
# AbstractAdapter Responsibility
|
204
|
+
@schema_cache = Sqlserver::SchemaCache.new self
|
205
|
+
@visitor = Arel::Visitors::SQLServer.new self
|
206
|
+
# Our Responsibility
|
207
|
+
@config = config
|
208
|
+
@connection_options = config
|
209
|
+
connect
|
210
|
+
@database_version = select_value 'SELECT @@version', 'SCHEMA'
|
211
|
+
@database_year = begin
|
212
|
+
if @database_version =~ /Azure/i
|
213
|
+
@sqlserver_azure = true
|
214
|
+
@database_version.match(/\s-\s([0-9.]+)/)[1]
|
215
|
+
year = 2012
|
216
|
+
else
|
217
|
+
year = DATABASE_VERSION_REGEXP.match(@database_version)[1]
|
218
|
+
year == "Denali" ? 2011 : year.to_i
|
219
|
+
end
|
220
|
+
rescue
|
221
|
+
0
|
222
|
+
end
|
223
|
+
@product_level = select_value "SELECT CAST(SERVERPROPERTY('productlevel') AS VARCHAR(128))", 'SCHEMA'
|
224
|
+
@product_version = select_value "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(128))", 'SCHEMA'
|
225
|
+
@edition = select_value "SELECT CAST(SERVERPROPERTY('edition') AS VARCHAR(128))", 'SCHEMA'
|
226
|
+
initialize_dateformatter
|
227
|
+
use_database
|
228
|
+
unless (@sqlserver_azure == true || SUPPORTED_VERSIONS.include?(@database_year))
|
229
|
+
raise NotImplementedError, "Currently, only #{SUPPORTED_VERSIONS.to_sentence} are supported. We got back #{@database_version}."
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# === Abstract Adapter ========================================== #
|
234
|
+
|
235
|
+
def adapter_name
|
236
|
+
ADAPTER_NAME
|
237
|
+
end
|
238
|
+
|
239
|
+
def supports_migrations?
|
240
|
+
true
|
241
|
+
end
|
242
|
+
|
243
|
+
def supports_primary_key?
|
244
|
+
true
|
245
|
+
end
|
246
|
+
|
247
|
+
def supports_count_distinct?
|
248
|
+
true
|
249
|
+
end
|
250
|
+
|
251
|
+
def supports_ddl_transactions?
|
252
|
+
true
|
253
|
+
end
|
254
|
+
|
255
|
+
def supports_bulk_alter?
|
256
|
+
false
|
257
|
+
end
|
258
|
+
|
259
|
+
def supports_savepoints?
|
260
|
+
true
|
261
|
+
end
|
262
|
+
|
263
|
+
def supports_index_sort_order?
|
264
|
+
true
|
265
|
+
end
|
266
|
+
|
267
|
+
def supports_explain?
|
268
|
+
true
|
269
|
+
end
|
270
|
+
|
271
|
+
def disable_referential_integrity
|
272
|
+
do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'"
|
273
|
+
yield
|
274
|
+
ensure
|
275
|
+
do_execute "EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'"
|
276
|
+
end
|
277
|
+
|
278
|
+
# === Abstract Adapter (Connection Management) ================== #
|
279
|
+
|
280
|
+
def active?
|
281
|
+
case @connection_options[:mode]
|
282
|
+
when :dblib
|
283
|
+
return @connection.active?
|
284
|
+
end
|
285
|
+
raw_connection_do("SELECT 1")
|
286
|
+
true
|
287
|
+
rescue *lost_connection_exceptions
|
288
|
+
false
|
289
|
+
end
|
290
|
+
|
291
|
+
def reconnect!
|
292
|
+
reset_transaction
|
293
|
+
disconnect!
|
294
|
+
connect
|
295
|
+
active?
|
296
|
+
end
|
297
|
+
|
298
|
+
def disconnect!
|
299
|
+
reset_transaction
|
300
|
+
@spid = nil
|
301
|
+
case @connection_options[:mode]
|
302
|
+
when :dblib
|
303
|
+
@connection.close rescue nil
|
304
|
+
when :odbc
|
305
|
+
@connection.disconnect rescue nil
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def reset!
|
310
|
+
remove_database_connections_and_rollback { }
|
311
|
+
end
|
312
|
+
|
313
|
+
# === Abstract Adapter (Misc Support) =========================== #
|
314
|
+
|
315
|
+
def pk_and_sequence_for(table_name)
|
316
|
+
idcol = identity_column(table_name)
|
317
|
+
idcol ? [idcol.name,nil] : nil
|
318
|
+
end
|
319
|
+
|
320
|
+
def primary_key(table_name)
|
321
|
+
identity_column(table_name).try(:name) || schema_cache.columns(table_name).detect(&:is_primary?).try(:name)
|
322
|
+
end
|
323
|
+
|
324
|
+
# === SQLServer Specific (DB Reflection) ======================== #
|
325
|
+
|
326
|
+
def sqlserver?
|
327
|
+
true
|
328
|
+
end
|
329
|
+
|
330
|
+
def sqlserver_2005?
|
331
|
+
@database_year == 2005
|
332
|
+
end
|
333
|
+
|
334
|
+
def sqlserver_2008?
|
335
|
+
@database_year == 2008
|
336
|
+
end
|
337
|
+
|
338
|
+
def sqlserver_2011?
|
339
|
+
@database_year == 2011
|
340
|
+
end
|
341
|
+
|
342
|
+
def sqlserver_2012?
|
343
|
+
@database_year == 2012
|
344
|
+
end
|
345
|
+
|
346
|
+
def sqlserver_azure?
|
347
|
+
@sqlserver_azure
|
348
|
+
end
|
349
|
+
|
350
|
+
def version
|
351
|
+
self.class::VERSION
|
352
|
+
end
|
353
|
+
|
354
|
+
def inspect
|
355
|
+
"#<#{self.class} version: #{version}, year: #{@database_year}, product_level: #{@product_level.inspect}, product_version: #{@product_version.inspect}, edition: #{@edition.inspect}, connection_options: #{@connection_options.inspect}>"
|
356
|
+
end
|
357
|
+
|
358
|
+
def auto_connect
|
359
|
+
@@auto_connect.is_a?(FalseClass) ? false : true
|
360
|
+
end
|
361
|
+
|
362
|
+
def auto_connect_duration
|
363
|
+
@@auto_connect_duration ||= 10
|
364
|
+
end
|
365
|
+
|
366
|
+
def native_string_database_type
|
367
|
+
@@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
|
368
|
+
end
|
369
|
+
|
370
|
+
def native_text_database_type
|
371
|
+
@@native_text_database_type || enable_default_unicode_types ? 'nvarchar(max)' : 'varchar(max)'
|
372
|
+
end
|
373
|
+
|
374
|
+
def native_time_database_type
|
375
|
+
sqlserver_2005? ? 'datetime' : 'time'
|
376
|
+
end
|
377
|
+
|
378
|
+
def native_date_database_type
|
379
|
+
sqlserver_2005? ? 'datetime' : 'date'
|
380
|
+
end
|
381
|
+
|
382
|
+
def native_binary_database_type
|
383
|
+
@@native_binary_database_type || 'varbinary(max)'
|
384
|
+
end
|
385
|
+
|
386
|
+
def cs_equality_operator
|
387
|
+
@@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS'
|
388
|
+
end
|
389
|
+
|
390
|
+
protected
|
391
|
+
|
392
|
+
# === Abstract Adapter (Misc Support) =========================== #
|
393
|
+
|
394
|
+
def translate_exception(e, message)
|
395
|
+
case message
|
396
|
+
when /(cannot insert duplicate key .* with unique index) | (violation of unique key constraint)/i
|
397
|
+
RecordNotUnique.new(message,e)
|
398
|
+
when /conflicted with the foreign key constraint/i
|
399
|
+
InvalidForeignKey.new(message,e)
|
400
|
+
when /has been chosen as the deadlock victim/i
|
401
|
+
DeadlockVictim.new(message,e)
|
402
|
+
when *lost_connection_messages
|
403
|
+
LostConnection.new(message,e)
|
404
|
+
else
|
405
|
+
super
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
# === SQLServer Specific (Connection Management) ================ #
|
410
|
+
|
411
|
+
def connect
|
412
|
+
config = @connection_options
|
413
|
+
@connection = case config[:mode]
|
414
|
+
when :dblib
|
415
|
+
appname = config[:appname] || configure_application_name || Rails.application.class.name.split('::').first rescue nil
|
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
|
449
|
+
when :odbc
|
450
|
+
if config[:dsn].include?(';')
|
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
|
466
|
+
end
|
467
|
+
@spid = _raw_select("SELECT @@SPID", :fetch => :rows).first.first
|
468
|
+
configure_connection
|
469
|
+
rescue
|
470
|
+
raise unless @auto_connecting
|
471
|
+
end
|
472
|
+
|
473
|
+
# Override this method so every connection can be configured to your needs.
|
474
|
+
# For example:
|
475
|
+
# raw_connection_do "SET TEXTSIZE #{64.megabytes}"
|
476
|
+
# raw_connection_do "SET CONCAT_NULL_YIELDS_NULL ON"
|
477
|
+
def configure_connection
|
478
|
+
end
|
479
|
+
|
480
|
+
# Override this method so every connection can have a unique name. Max 30 characters. Used by TinyTDS only.
|
481
|
+
# For example:
|
482
|
+
# "myapp_#{$$}_#{Thread.current.object_id}".to(29)
|
483
|
+
def configure_application_name
|
484
|
+
end
|
485
|
+
|
486
|
+
def initialize_dateformatter
|
487
|
+
@database_dateformat = user_options_dateformat
|
488
|
+
a, b, c = @database_dateformat.each_char.to_a
|
489
|
+
[a,b,c].each { |f| f.upcase! if f == 'y' }
|
490
|
+
dateformat = "%#{a}-%#{b}-%#{c}"
|
491
|
+
::Date::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
|
492
|
+
::Time::DATE_FORMATS[:_sqlserver_dateformat] = dateformat
|
493
|
+
end
|
494
|
+
|
495
|
+
def remove_database_connections_and_rollback(database=nil)
|
496
|
+
database ||= current_database
|
497
|
+
do_execute "ALTER DATABASE #{quote_table_name(database)} SET SINGLE_USER WITH ROLLBACK IMMEDIATE"
|
498
|
+
begin
|
499
|
+
yield
|
500
|
+
ensure
|
501
|
+
do_execute "ALTER DATABASE #{quote_table_name(database)} SET MULTI_USER"
|
502
|
+
end if block_given?
|
503
|
+
end
|
504
|
+
|
505
|
+
def with_sqlserver_error_handling
|
506
|
+
begin
|
507
|
+
yield
|
508
|
+
rescue Exception => e
|
509
|
+
case translate_exception(e,e.message)
|
510
|
+
when LostConnection; retry if auto_reconnected?
|
511
|
+
end
|
512
|
+
raise
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
def disable_auto_reconnect
|
517
|
+
old_auto_connect, self.class.auto_connect = self.class.auto_connect, false
|
518
|
+
yield
|
519
|
+
ensure
|
520
|
+
self.class.auto_connect = old_auto_connect
|
521
|
+
end
|
522
|
+
|
523
|
+
def auto_reconnected?
|
524
|
+
return false unless auto_connect
|
525
|
+
@auto_connecting = true
|
526
|
+
count = 0
|
527
|
+
while count <= (auto_connect_duration / 2)
|
528
|
+
result = reconnect!
|
529
|
+
ActiveRecord::Base.did_retry_sqlserver_connection(self,count)
|
530
|
+
return true if result
|
531
|
+
sleep 2** count
|
532
|
+
count += 1
|
533
|
+
end
|
534
|
+
ActiveRecord::Base.did_lose_sqlserver_connection(self)
|
535
|
+
false
|
536
|
+
ensure
|
537
|
+
@auto_connecting = false
|
538
|
+
end
|
539
|
+
|
540
|
+
end #class SQLServerAdapter < AbstractAdapter
|
541
|
+
|
542
|
+
end #module ConnectionAdapters
|
543
|
+
|
544
|
+
end #module ActiveRecord
|
545
|
+
|